Merge "Add missing frame info name" into sc-dev
diff --git a/Android.bp b/Android.bp
index 0a3ca3b..240d803 100644
--- a/Android.bp
+++ b/Android.bp
@@ -403,6 +403,7 @@
         ":framework-mediaprovider-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
+        ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
@@ -423,6 +424,7 @@
         "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",
@@ -443,6 +445,7 @@
         "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 3f2e898..4bd524f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -315,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",
@@ -338,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",
@@ -377,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 8fcd2f9..73ca0cc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -108,54 +108,85 @@
      * 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.
      *
-     * <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>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.
      *
-     * @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
      */
     public void setSchema(
             @NonNull SetSchemaRequest request,
@@ -193,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);
                                 }
@@ -256,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
@@ -297,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
@@ -377,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(
@@ -440,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.
      *
@@ -479,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
@@ -520,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/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 2f1817e..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;
@@ -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/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index b2ffd5b..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,10 +77,14 @@
      *       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
@@ -109,10 +113,8 @@
      * 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
      */
@@ -144,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);
@@ -219,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/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 6650e67..fd35537 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -22,6 +22,7 @@
         ],
     },
     libs: [
+        "app-compat-annotations",
         "framework-minus-apex",
         "unsupportedappusage",
     ],
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7851087..7c7b210 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -16,11 +16,14 @@
 
 package android.app;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -183,6 +186,25 @@
     @UnsupportedAppUsage
     public static final int FLAG_IDLE_UNTIL = 1<<4;
 
+    /**
+     * Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less
+     * than {@link Build.VERSION_CODES#S}
+     * @hide
+     */
+    public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
+
+    /**
+     * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
+     * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
+     * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
+     * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+     *
+     * @hide
+     */
+    @ChangeId
+    @Disabled // TODO (b/171306433): Enable starting S.
+    public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
+
     @UnsupportedAppUsage
     private final IAlarmManager mService;
     private final Context mContext;
@@ -588,6 +610,11 @@
      * This method is like {@link #setExact(int, long, PendingIntent)}, but implies
      * {@link #RTC_WAKEUP}.
      *
+     * <p>
+     * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. Alarms scheduled via this API
+     * will be allowed to start a foreground service even if the app is in the background.
+     *
      * @param info
      * @param operation Action to perform when the alarm goes off;
      *        typically comes from {@link PendingIntent#getBroadcast
@@ -603,6 +630,7 @@
      * @see android.content.Context#registerReceiver
      * @see android.content.Intent#filterEquals
      */
+    @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
     public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
         setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
                 null, null, null, null, info);
@@ -876,6 +904,12 @@
      * device is idle it may take even more liberties with scheduling in order to optimize
      * for battery life.</p>
      *
+     * <p>
+     * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission, unless the app is exempt from
+     * battery restrictions. Alarms scheduled via this API will be allowed to start a foreground
+     * service even if the app is in the background.
+     *
      * @param type type of alarm.
      * @param triggerAtMillis time in milliseconds that the alarm should go
      *        off, using the appropriate clock (depending on the alarm type).
@@ -895,6 +929,7 @@
      * @see #RTC
      * @see #RTC_WAKEUP
      */
+    @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
     public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
             PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
@@ -1018,6 +1053,18 @@
     }
 
     /**
+     * Called to check if the caller has permission to use alarms set via {@link }
+     * @return
+     */
+    public boolean canScheduleExactAlarms() {
+        try {
+            return mService.canScheduleExactAlarms();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets information about the next alarm clock currently scheduled.
      *
      * The alarm clocks considered are those scheduled by any application
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
index 2c51935..2f21ce3 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -41,4 +41,5 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
     long currentNetworkTimeMillis();
+    boolean canScheduleExactAlarms();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index 657c368..3bc7b30 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -26,6 +26,7 @@
 import android.app.AlarmManager;
 import android.app.IAlarmListener;
 import android.app.PendingIntent;
+import android.os.Bundle;
 import android.os.WorkSource;
 import android.util.IndentingPrintWriter;
 import android.util.TimeUtils;
@@ -92,10 +93,12 @@
     private long mWhenElapsed;
     private long mMaxWhenElapsed;
     public AlarmManagerService.PriorityClass priorityClass;
+    /** Broadcast options to use when delivering this alarm */
+    public Bundle mIdleOptions;
 
     Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
             PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
-            AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
+            AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions) {
         this.type = type;
         origWhen = when;
         wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -115,6 +118,7 @@
         alarmClock = info;
         this.uid = uid;
         packageName = pkgName;
+        mIdleOptions = idleOptions;
         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
         creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
     }
@@ -303,6 +307,10 @@
             ipw.print("listener=");
             ipw.println(listener.asBinder());
         }
+        if (mIdleOptions != null) {
+            ipw.print("idle-options=");
+            ipw.println(mIdleOptions.toString());
+        }
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index f6a1b8a..559a434 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -20,11 +20,15 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
+import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
+import static android.app.AlarmManager.INTERVAL_HOUR;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
@@ -32,6 +36,7 @@
 import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -43,12 +48,14 @@
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
 import android.os.BatteryManager;
@@ -65,6 +72,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -95,6 +103,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.LocalLog;
@@ -177,6 +187,7 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    IAppOpsService mAppOpsService;
     DeviceIdleInternal mLocalDeviceIdleController;
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@
             "REORDER_ALARMS_FOR_STANDBY",
     });
 
-    /**
-     * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
-     */
-    Bundle mIdleOptions;
+    BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
+    BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
 
     // TODO(b/172085676): Move inside alarm store.
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -410,6 +419,10 @@
         @VisibleForTesting
         static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
 
+        @VisibleForTesting
+        static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota";
+        private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window";
+
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -433,8 +446,14 @@
         private static final boolean DEFAULT_LAZY_BATCHING = true;
         private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
 
-        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
-        public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
+        /**
+         * Default quota for pre-S apps. Enough to accommodate the existing policy of an alarm
+         * every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes.
+         */
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7;
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72;
+
+        private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
 
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -463,6 +482,19 @@
 
         public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
 
+        /**
+         * Used to provide backwards compatibility to pre-S apps with a quota equivalent to the
+         * earlier delay throttling mechanism.
+         */
+        public int ALLOW_WHILE_IDLE_COMPAT_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+
+        /**
+         * The window used for enforcing {@link #ALLOW_WHILE_IDLE_QUOTA} and
+         * {@link #ALLOW_WHILE_IDLE_COMPAT_QUOTA}. Can be configured, but only recommended for
+         * testing.
+         */
+        public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
 
         Constants() {
@@ -480,9 +512,11 @@
         public void updateAllowWhileIdleWhitelistDurationLocked() {
             if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
                 mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
-                BroadcastOptions opts = BroadcastOptions.makeBasic();
-                opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
-                mIdleOptions = opts.toBundle();
+
+                mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+                mOptsWithoutFgs.setTemporaryAppWhitelistDuration(
+                        TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+                        ALLOW_WHILE_IDLE_WHITELIST_DURATION);
             }
         }
 
@@ -516,6 +550,27 @@
                                 ALLOW_WHILE_IDLE_QUOTA = 1;
                             }
                             break;
+                        case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
+                            ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
+                                    KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
+                                    DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+                            if (ALLOW_WHILE_IDLE_COMPAT_QUOTA <= 0) {
+                                Slog.w(TAG, "Cannot have quota lower than 1.");
+                                ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1;
+                            }
+                            break;
+                        case KEY_ALLOW_WHILE_IDLE_WINDOW:
+                            ALLOW_WHILE_IDLE_WINDOW = properties.getLong(
+                                    KEY_ALLOW_WHILE_IDLE_WINDOW, DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+                            if (ALLOW_WHILE_IDLE_WINDOW > DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+                                Slog.w(TAG, "Cannot have allow_while_idle_window > "
+                                        + DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+                                ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+                            } else if (ALLOW_WHILE_IDLE_WINDOW < DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+                                Slog.w(TAG, "Using a non-default allow_while_idle_window = "
+                                        + ALLOW_WHILE_IDLE_WINDOW);
+                            }
+                            break;
                         case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
                             ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
                                     KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
@@ -643,10 +698,14 @@
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print("allow_while_idle_window=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW);
+            pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
             pw.println();
 
+            pw.print(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+            pw.println();
+
             pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
             pw.println();
 
@@ -830,7 +889,15 @@
         if (futurity < MIN_FUZZABLE_INTERVAL) {
             futurity = 0;
         }
-        return clampPositive(triggerAtTime + (long) (.75 * futurity));
+        long maxElapsed = triggerAtTime + (long) (0.75 * futurity);
+        // For non-repeating alarms, window is capped at a maximum of one hour from the requested
+        // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable.
+        // In practice, the delivery window should generally be much smaller than that
+        // when the device is not idling.
+        if (interval == 0) {
+            maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR);
+        }
+        return clampPositive(maxElapsed);
     }
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
@@ -998,7 +1065,7 @@
                 setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
                         nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
                         null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
-                        alarm.packageName);
+                        alarm.packageName, null);
                 // Kernel alarms will be rescheduled as needed in setImplLocked
             }
         }
@@ -1241,7 +1308,8 @@
             mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
 
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
-            mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
+            mAllowWhileIdleHistory = new AppWakeupHistory(
+                    Constants.DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1327,6 +1395,28 @@
             synchronized (mLock) {
                 mConstants.start();
                 mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+                mAppOpsService = mInjector.getAppOpsService();
+                try {
+                    mAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
+                            new IAppOpsCallback.Stub() {
+                                @Override
+                                public void opChanged(int op, int uid, String packageName)
+                                        throws RemoteException {
+                                    if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) {
+                                        return;
+                                    }
+                                    final int mode = mAppOpsService.checkOperation(op, uid,
+                                            packageName);
+                                    if (mode != AppOpsManager.MODE_ALLOWED
+                                            && mode != AppOpsManager.MODE_DEFAULT) {
+                                        mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
+                                                uid, 0, packageName).sendToTarget();
+                                    }
+                                }
+                            });
+                } catch (RemoteException e) {
+                }
+
                 mLocalDeviceIdleController =
                         LocalServices.getService(DeviceIdleInternal.class);
                 mUsageStatsManagerInternal =
@@ -1428,7 +1518,7 @@
     void setImpl(int type, long triggerAtTime, long windowLength, long interval,
             PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
             int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
-            int callingUid, String callingPackage) {
+            int callingUid, String callingPackage, Bundle idleOptions) {
         if ((operation == null && directReceiver == null)
                 || (operation != null && directReceiver != null)) {
             Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
@@ -1519,17 +1609,18 @@
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
                     directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
-                    callingPackage);
+                    callingPackage, idleOptions);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
             long interval, PendingIntent operation, IAlarmListener directReceiver,
             String listenerTag, int flags, WorkSource workSource,
-            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
+            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage,
+            Bundle idleOptions) {
         final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
-                callingUid, callingPackage);
+                callingUid, callingPackage, idleOptions);
         if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
             Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                     + " -- package not allowed to start");
@@ -1605,19 +1696,21 @@
             return false;
         }
 
-        if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver(
-                alarm.creatorUid, alarm.sourcePackage))) {
+        if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver(
+                alarm.creatorUid, alarm.sourcePackage)) {
             return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
         }
 
         final long batterySaverPolicyElapsed;
-        if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
+        if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
             // Unrestricted.
             batterySaverPolicyElapsed = nowElapsed;
-        } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+        } else if (isAllowedWhileIdleRestricted(alarm)) {
             // Allowed but limited.
             final int userId = UserHandle.getUserId(alarm.creatorUid);
-            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+                    ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+                    : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
             final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
             if (dispatchesInWindow < quota) {
@@ -1625,7 +1718,7 @@
                 batterySaverPolicyElapsed = nowElapsed;
             } else {
                 batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
-                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                        alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
             }
         } else {
             // Not allowed.
@@ -1635,6 +1728,16 @@
     }
 
     /**
+     * Returns {@code true} if the given alarm has the flag
+     * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE} or
+     * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE_COMPAT}
+     *
+     */
+    private static boolean isAllowedWhileIdleRestricted(Alarm a) {
+        return (a.flags & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT)) != 0;
+    }
+
+    /**
      * Adjusts the delivery time of the alarm based on device_idle (doze) rules.
      *
      * @param alarm The alarm to adjust
@@ -1647,14 +1750,15 @@
         }
 
         final long deviceIdlePolicyTime;
-        if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
-                | AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) {
+        if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) {
             // Unrestricted.
             deviceIdlePolicyTime = nowElapsed;
-        } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+        } else if (isAllowedWhileIdleRestricted(alarm)) {
             // Allowed but limited.
             final int userId = UserHandle.getUserId(alarm.creatorUid);
-            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+                    ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+                    : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
             final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
             if (dispatchesInWindow < quota) {
@@ -1662,7 +1766,7 @@
                 deviceIdlePolicyTime = nowElapsed;
             } else {
                 final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
-                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                        alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
                 deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
             }
         } else {
@@ -1749,7 +1853,7 @@
         } else if (mPendingIdleUntil != null) {
             adjustDeliveryTimeBasedOnDeviceIdle(a);
         }
-        if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+        if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) {
             if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
                     > a.getWhenElapsed()) {
                 mNextWakeFromIdle = a;
@@ -1810,6 +1914,14 @@
     }
 
     /**
+     * Returns true if the given uid is on the system or user's power save exclusion list.
+     */
+    boolean isWhitelisted(int uid) {
+        return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist(
+                UserHandle.getAppId(uid)));
+    }
+
+    /**
      * Public-facing binder interface
      */
     private final IBinder mService = new IAlarmManager.Stub() {
@@ -1824,6 +1936,54 @@
             // wakelock time spent in alarm delivery
             mAppOps.checkPackage(callingUid, callingPackage);
 
+            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+
+            Bundle idleOptions = null;
+            if (alarmClock != null || allowWhileIdle) {
+                // make sure the caller is allowed to use the requested kind of alarm, and also
+                // decide what broadcast options to use.
+                final boolean needsPermission;
+                boolean lowQuota;
+                if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+                        callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
+                    if (windowLength != AlarmManager.WINDOW_EXACT) {
+                        needsPermission = false;
+                        lowQuota = true;
+                        idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle()
+                                : mOptsWithoutFgs.toBundle();
+                    } else if (alarmClock != null) {
+                        needsPermission = true;
+                        lowQuota = false;
+                        idleOptions = mOptsWithFgs.toBundle();
+                    } else {
+                        needsPermission = true;
+                        lowQuota = false;
+                        idleOptions = mOptsWithFgs.toBundle();
+                    }
+                } else {
+                    needsPermission = false;
+                    lowQuota = allowWhileIdle;
+                    idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+                }
+                if (needsPermission && !canScheduleExactAlarms()) {
+                    if (alarmClock == null && isWhitelisted(callingUid)) {
+                        // If the app is on the full system allow-list (not except-idle), we still
+                        // allow the alarms, but with a lower quota to keep pre-S compatibility.
+                        lowQuota = true;
+                    } else {
+                        final String errorMessage = "Caller needs to hold "
+                                + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
+                                + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
+                                + " alarms.";
+                        throw new SecurityException(errorMessage);
+                    }
+                }
+                if (lowQuota) {
+                    flags &= ~FLAG_ALLOW_WHILE_IDLE;
+                    flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
+                }
+            }
+
             // Repeating alarms must use PendingIntent, not direct listener
             if (interval != 0) {
                 if (directReceiver != null) {
@@ -1840,8 +2000,7 @@
 
             // No incoming callers can request either WAKE_FROM_IDLE or
             // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
-            flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
-                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
+            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
 
             // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
             // manager when to come out of idle mode, which is only for DeviceIdleController.
@@ -1857,22 +2016,32 @@
             // If this alarm is for an alarm clock, then it must be standalone and we will
             // use it to wake early from idle if needed.
             if (alarmClock != null) {
-                flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+                flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
 
             // If the caller is a core system component or on the user's whitelist, and not calling
             // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
             // This means we will allow these alarms to go off as normal even while idle, with no
             // timing restrictions.
-            } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
+            } else if (workSource == null && (UserHandle.isCore(callingUid)
                     || UserHandle.isSameApp(callingUid, mSystemUiUid)
                     || ((mAppStateTracker != null)
                         && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
-                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
-                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+                flags &= ~FLAG_ALLOW_WHILE_IDLE;
+                flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
+                idleOptions = null;
             }
 
             setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
-                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
+                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
+                    idleOptions);
+        }
+
+        @Override
+        public boolean canScheduleExactAlarms() {
+            return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(),
+                    Manifest.permission.SCHEDULE_EXACT_ALARM)
+                    == PermissionChecker.PERMISSION_GRANTED;
         }
 
         @Override
@@ -2755,6 +2924,77 @@
         }
     }
 
+    /**
+     * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
+     * that the app is no longer eligible to use.
+     * TODO (b/179541791): Revisit and write tests once UX is final.
+     */
+    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
+        if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
+            return;
+        }
+        if (isWhitelisted(uid)) {
+            return;
+        }
+        if (!CompatChanges.isChangeEnabled(
+                AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+                packageName, UserHandle.getUserHandleForUid(uid))) {
+            return;
+        }
+
+        final Predicate<Alarm> whichAlarms =
+                a -> (a.uid == uid && a.packageName.equals(packageName)
+                        && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
+        final ArrayList<Alarm> removed = mAlarmStore.remove(whichAlarms);
+        final boolean didRemove = !removed.isEmpty();
+        if (didRemove) {
+            decrementAlarmCount(uid, removed.size());
+        }
+
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+            for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
+                final Alarm alarm = alarmsForUid.get(j);
+                if (whichAlarms.test(alarm)) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    alarmsForUid.remove(j);
+                    decrementAlarmCount(alarm.uid, 1);
+                }
+            }
+            if (alarmsForUid.size() == 0) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
+        for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingNonWakeupAlarms.get(i);
+            if (whichAlarms.test(a)) {
+                // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                mPendingNonWakeupAlarms.remove(i);
+                decrementAlarmCount(a.uid, 1);
+            }
+        }
+
+        if (didRemove) {
+            if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
+                mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+                if (mPendingIdleUntil != null) {
+                    final boolean idleUntilUpdated = mAlarmStore.updateAlarmDeliveries(alarm -> {
+                        if (alarm != mPendingIdleUntil) {
+                            return false;
+                        }
+                        return adjustIdleUntilTime(alarm);
+                    });
+                    if (idleUntilUpdated) {
+                        mAlarmStore.updateAlarmDeliveries(
+                                alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm));
+                    }
+                }
+            }
+            rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
+        }
+    }
+
     void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
         if (operation == null && directReceiver == null) {
             if (localLOGV) {
@@ -3082,7 +3322,7 @@
         }
     }
 
-    private boolean isExemptFromBatterySaver(Alarm alarm) {
+    private static boolean isExemptFromBatterySaver(Alarm alarm) {
         if (alarm.alarmClock != null) {
             return true;
         }
@@ -3142,7 +3382,7 @@
 
             alarm.count = 1;
             triggerList.add(alarm);
-            if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+            if ((alarm.flags & FLAG_WAKE_FROM_IDLE) != 0) {
                 EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
                         alarm.statsTag);
             }
@@ -3180,7 +3420,7 @@
                 setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
                         nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
                         null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
-                        alarm.packageName);
+                        alarm.packageName, null);
             }
 
             if (alarm.wakeup) {
@@ -3257,7 +3497,6 @@
         mLastAlarmDeliveryTime = nowELAPSED;
         for (int i = 0; i < triggerList.size(); i++) {
             Alarm alarm = triggerList.get(i);
-            final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
             if (alarm.wakeup) {
                 Trace.traceBegin(Trace.TRACE_TAG_POWER,
                         "Dispatch wakeup alarm to " + alarm.packageName);
@@ -3273,7 +3512,7 @@
                     mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource,
                             alarm.uid, alarm.statsTag);
                 }
-                mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
+                mDeliveryTracker.deliverLocked(alarm, nowELAPSED);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
@@ -3282,9 +3521,10 @@
         }
     }
 
-    private boolean isExemptFromAppStandby(Alarm a) {
+    @VisibleForTesting
+    static boolean isExemptFromAppStandby(Alarm a) {
         return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
-                || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+                || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0;
     }
 
     @VisibleForTesting
@@ -3368,6 +3608,11 @@
                     MATCH_SYSTEM_ONLY, USER_SYSTEM);
         }
 
+        IAppOpsService getAppOpsService() {
+            return IAppOpsService.Stub.asInterface(
+                    ServiceManager.getService(Context.APP_OPS_SERVICE));
+        }
+
         ClockReceiver getClockReceiver(AlarmManagerService service) {
             return service.new ClockReceiver();
         }
@@ -3566,6 +3811,7 @@
         public static final int APP_STANDBY_BUCKET_CHANGED = 5;
         public static final int CHARGING_STATUS_CHANGED = 6;
         public static final int REMOVE_FOR_CANCELED = 7;
+        public static final int REMOVE_EXACT_ALARMS = 8;
 
         AlarmHandler() {
             super(Looper.myLooper());
@@ -3645,6 +3891,14 @@
                     }
                     break;
 
+                case REMOVE_EXACT_ALARMS:
+                    final int uid = msg.arg1;
+                    final String packageName = (String) msg.obj;
+                    synchronized (mLock) {
+                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -3720,7 +3974,7 @@
 
             setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
                     0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
-                    Process.myUid(), "android");
+                    Process.myUid(), "android", null);
 
             // Finally, remember when we set the tick alarm
             synchronized (mLock) {
@@ -3740,7 +3994,7 @@
             final WorkSource workSource = null; // Let system take blame for date change events.
             setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
                     AlarmManager.FLAG_STANDALONE, workSource, null,
-                    Process.myUid(), "android");
+                    Process.myUid(), "android", null);
         }
     }
 
@@ -4112,7 +4366,7 @@
          * Deliver an alarm and set up the post-delivery handling appropriately
          */
         @GuardedBy("mLock")
-        public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+        public void deliverLocked(Alarm alarm, long nowELAPSED) {
             final long workSourceToken = ThreadLocalWorkSource.setUid(
                     getAlarmAttributionUid(alarm));
             try {
@@ -4122,10 +4376,8 @@
 
                     try {
                         alarm.operation.send(getContext(), 0,
-                                mBackgroundIntent.putExtra(
-                                        Intent.EXTRA_ALARM_COUNT, alarm.count),
-                                mDeliveryTracker, mHandler, null,
-                                allowWhileIdle ? mIdleOptions : null);
+                                mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
                     } catch (PendingIntent.CanceledException e) {
                         if (alarm.repeatInterval > 0) {
                             // This IntentSender is no longer valid, but this
@@ -4194,7 +4446,7 @@
             if (inflight.isBroadcast()) {
                 notifyBroadcastAlarmPendingLocked(alarm.uid);
             }
-            if (allowWhileIdle) {
+            if (isAllowedWhileIdleRestricted(alarm)) {
                 final boolean doze = (mPendingIdleUntil != null);
                 final boolean batterySaver = (mAppStateTracker != null
                         && mAppStateTracker.isForceAllAppsStandbyEnabled());
@@ -4204,8 +4456,7 @@
                     mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
                             UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
                     mAlarmStore.updateAlarmDeliveries(a -> {
-                        if (a.creatorUid != alarm.creatorUid
-                                || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+                        if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
                             return false;
                         }
                         return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
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 3cefe65..164781a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -741,17 +741,26 @@
 
         pw.increaseIndent();
         try {
-            pw.print("Configuration:");
+            pw.println("Configuration:");
             pw.increaseIndent();
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
+            pw.println();
             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
+            pw.println();
             pw.decreaseIndent();
 
             pw.print("Screen state: current ");
@@ -770,18 +779,17 @@
 
             pw.println();
 
-            pw.println("Current max jobs:");
-            pw.println("  ");
+            pw.print("Current work counts: ");
             pw.println(mWorkCountTracker);
 
             pw.println();
 
             pw.print("mLastMemoryTrimLevel: ");
-            pw.print(mLastMemoryTrimLevel);
+            pw.println(mLastMemoryTrimLevel);
             pw.println();
 
             pw.print("User Grace Period: ");
-            pw.print(mGracePeriodObserver.mGracePeriodExpiration);
+            pw.println(mGracePeriodObserver.mGracePeriodExpiration);
             pw.println();
 
             mStatLogger.dump(pw);
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 96f3bcc..fdbc086 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3040,7 +3040,6 @@
             pw.println();
 
             for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
-                pw.print("    ");
                 mJobRestrictions.get(i).dumpConstants(pw);
                 pw.println();
             }
@@ -3067,7 +3066,6 @@
 
                     job.dump(pw, "    ", true, nowElapsed);
 
-
                     pw.print("    Restricted due to:");
                     final boolean isRestricted = checkIfRestricted(job) != null;
                     if (isRestricted) {
@@ -3161,39 +3159,28 @@
             }
             pw.println();
             pw.println("Active jobs:");
+            pw.increaseIndent();
             for (int i=0; i<mActiveServices.size(); i++) {
                 JobServiceContext jsc = mActiveServices.get(i);
-                pw.print("  Slot #"); pw.print(i); pw.print(": ");
-                final JobStatus job = jsc.getRunningJobLocked();
-                if (job == null) {
-                    if (jsc.mStoppedReason != null) {
-                        pw.print("inactive since ");
-                        TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
-                        pw.print(", stopped because: ");
-                        pw.println(jsc.mStoppedReason);
-                    } else {
-                        pw.println("inactive");
-                    }
-                    continue;
-                } else {
-                    pw.println(job.toShortString());
-                    pw.print("    Running for: ");
-                    TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
-                    pw.print(", timeout at: ");
-                    TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
-                    pw.println();
-                    job.dump(pw, "    ", false, nowElapsed);
-                    int priority = evaluateJobPriorityLocked(job);
-                    pw.print("    Evaluated priority: ");
-                    pw.println(JobInfo.getPriorityString(priority));
+                pw.print("Slot #"); pw.print(i); pw.print(": ");
+                jsc.dumpLocked(pw, nowElapsed);
 
-                    pw.print("    Active at ");
+                final JobStatus job = jsc.getRunningJobLocked();
+                if (job != null) {
+                    pw.increaseIndent();
+                    job.dump(pw, "  ", false, nowElapsed);
+                    pw.print("Evaluated priority: ");
+                    pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority));
+
+                    pw.print("Active at ");
                     TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
                     pw.print(", pending for ");
                     TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
                     pw.println();
+                    pw.decreaseIndent();
                 }
             }
+            pw.decreaseIndent();
             if (filterUid == -1) {
                 pw.println();
                 pw.print("mReadyToRock="); pw.println(mReadyToRock);
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 da6f9fe..0aca246 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -43,6 +43,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.TimeUtils;
 
@@ -901,8 +902,30 @@
         mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
     }
 
-
     private void removeOpTimeOutLocked() {
         mCallbackHandler.removeMessages(MSG_TIMEOUT);
     }
+
+    void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
+        if (mRunningJob == null) {
+            if (mStoppedReason != null) {
+                pw.print("inactive since ");
+                TimeUtils.formatDuration(mStoppedTime, nowElapsed, pw);
+                pw.print(", stopped because: ");
+                pw.println(mStoppedReason);
+            } else {
+                pw.println("inactive");
+            }
+        } else {
+            pw.println(mRunningJob.toShortString());
+
+            pw.increaseIndent();
+            pw.print("Running for: ");
+            TimeUtils.formatDuration(nowElapsed - mExecutionStartTimeElapsed, pw);
+            pw.print(", timeout at: ");
+            TimeUtils.formatDuration(mTimeoutElapsed - nowElapsed, pw);
+            pw.println();
+            pw.decreaseIndent();
+        }
+    }
 }
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/api/Android.bp b/api/Android.bp
index d5c6bf6..5466bd2 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -37,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}",
@@ -63,6 +64,22 @@
 }
 
 genrule {
+    name: "frameworks-base-api-current-compat",
+    srcs: [
+        ":android.api.public.latest",
+        ":android-incompatibilities.api.public.latest",
+        ":frameworks-base-api-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.public.latest) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
+        "$(location :frameworks-base-api-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-current.srcjar",
     srcs: [
         ":android.net.ipsec.ike{.public.stubs.source}",
@@ -75,6 +92,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}",
@@ -99,6 +117,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}",
@@ -133,6 +152,7 @@
         ":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}",
@@ -158,6 +178,24 @@
 }
 
 genrule {
+    name: "frameworks-base-api-system-current-compat",
+    srcs: [
+        ":android.api.system.latest",
+        ":android-incompatibilities.api.system.latest",
+        ":frameworks-base-api-current.txt",
+        ":frameworks-base-api-system-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.system.latest) " +
+        "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
+        "$(location :frameworks-base-api-system-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-system-removed.txt",
     srcs: [
         ":android.net.ipsec.ike{.system.removed-api.txt}",
@@ -167,6 +205,7 @@
         ":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}",
@@ -201,6 +240,7 @@
         ":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}",
@@ -225,6 +265,27 @@
 }
 
 genrule {
+    name: "frameworks-base-api-module-lib-current-compat",
+    srcs: [
+        ":android.api.module-lib.latest",
+        ":android-incompatibilities.api.module-lib.latest",
+        ":frameworks-base-api-current.txt",
+        ":frameworks-base-api-module-lib-current.txt",
+    ],
+    out: ["stdout.txt"],
+    tools: ["metalava"],
+    cmd: "$(location metalava) --no-banner --format=v2 " +
+        "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+        // Note: having "public" be the base of module-lib is not perfect -- it should
+        // ideally be a merged public+system), but this will  help when migrating from
+        // MODULE_LIBS -> public.
+        "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+        "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
+        "$(location :frameworks-base-api-module-lib-current.txt) " +
+        "> $(genDir)/stdout.txt",
+}
+
+genrule {
     name: "frameworks-base-api-module-lib-removed.txt",
     srcs: [
         ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
@@ -234,6 +295,7 @@
         ":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 3bad889..5d64579 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -26,10 +26,24 @@
     tidy_checks_as_errors: [
         "modernize-*",
         "-modernize-avoid-c-arrays",
+        "-modernize-pass-by-value",
+        "-modernize-replace-disallow-copy-and-assign-macro",
+        "-modernize-use-equals-default",
+        "-modernize-use-nodiscard",
+        "-modernize-use-override",
         "-modernize-use-trailing-return-type",
+        "-modernize-use-using",
         "android-*",
         "misc-*",
+        "-misc-non-private-member-variables-in-classes",
         "readability-*",
+        "-readability-braces-around-statements",
+        "-readability-const-return-type",
+        "-readability-convert-member-functions-to-static",
+        "-readability-else-after-return",
+        "-readability-named-parameter",
+        "-readability-redundant-access-specifiers",
+        "-readability-uppercase-literal-suffix",
     ],
     tidy_flags: [
         "-system-headers",
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/core/api/current.txt b/core/api/current.txt
index 0eb591d..d3d754c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -88,6 +88,7 @@
     field @Deprecated public static final String GET_TASKS = "android.permission.GET_TASKS";
     field public static final String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
     field public static final String HIDE_OVERLAY_WINDOWS = "android.permission.HIDE_OVERLAY_WINDOWS";
+    field public static final String HIGH_SAMPLING_RATE_SENSORS = "android.permission.HIGH_SAMPLING_RATE_SENSORS";
     field public static final String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
     field public static final String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
     field public static final String INSTALL_SHORTCUT = "com.android.launcher.permission.INSTALL_SHORTCUT";
@@ -144,6 +145,7 @@
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final String SEND_SMS = "android.permission.SEND_SMS";
     field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -959,6 +961,8 @@
     field public static final int maxLines = 16843091; // 0x1010153
     field public static final int maxLongVersionCode = 16844163; // 0x1010583
     field public static final int maxRecents = 16843846; // 0x1010446
+    field public static final int maxResizeHeight = 16844339; // 0x1010633
+    field public static final int maxResizeWidth = 16844338; // 0x1010632
     field public static final int maxRows = 16843059; // 0x1010133
     field public static final int maxSdkVersion = 16843377; // 0x1010271
     field public static final int maxWidth = 16843039; // 0x101011f
@@ -1185,6 +1189,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
@@ -1295,6 +1300,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
@@ -1391,6 +1397,8 @@
     field public static final int tabWidgetStyle = 16842883; // 0x1010083
     field public static final int tag = 16842961; // 0x10100d1
     field public static final int targetActivity = 16843266; // 0x1010202
+    field public static final int targetCellHeight = 16844341; // 0x1010635
+    field public static final int targetCellWidth = 16844340; // 0x1010634
     field public static final int targetClass = 16842799; // 0x101002f
     field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
     field public static final int targetId = 16843740; // 0x10103dc
@@ -1655,6 +1663,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
@@ -1702,30 +1714,42 @@
     field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006
     field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007
     field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008
-    field public static final int system_accent_0 = 17170473; // 0x1060029
-    field public static final int system_accent_100 = 17170475; // 0x106002b
-    field public static final int system_accent_1000 = 17170484; // 0x1060034
-    field public static final int system_accent_200 = 17170476; // 0x106002c
-    field public static final int system_accent_300 = 17170477; // 0x106002d
-    field public static final int system_accent_400 = 17170478; // 0x106002e
-    field public static final int system_accent_50 = 17170474; // 0x106002a
-    field public static final int system_accent_500 = 17170479; // 0x106002f
-    field public static final int system_accent_600 = 17170480; // 0x1060030
-    field public static final int system_accent_700 = 17170481; // 0x1060031
-    field public static final int system_accent_800 = 17170482; // 0x1060032
-    field public static final int system_accent_900 = 17170483; // 0x1060033
-    field public static final int system_main_0 = 17170461; // 0x106001d
-    field public static final int system_main_100 = 17170463; // 0x106001f
-    field public static final int system_main_1000 = 17170472; // 0x1060028
-    field public static final int system_main_200 = 17170464; // 0x1060020
-    field public static final int system_main_300 = 17170465; // 0x1060021
-    field public static final int system_main_400 = 17170466; // 0x1060022
-    field public static final int system_main_50 = 17170462; // 0x106001e
-    field public static final int system_main_500 = 17170467; // 0x1060023
-    field public static final int system_main_600 = 17170468; // 0x1060024
-    field public static final int system_main_700 = 17170469; // 0x1060025
-    field public static final int system_main_800 = 17170470; // 0x1060026
-    field public static final int system_main_900 = 17170471; // 0x1060027
+    field public static final int system_neutral_0 = 17170485; // 0x1060035
+    field public static final int system_neutral_100 = 17170487; // 0x1060037
+    field public static final int system_neutral_1000 = 17170496; // 0x1060040
+    field public static final int system_neutral_200 = 17170488; // 0x1060038
+    field public static final int system_neutral_300 = 17170489; // 0x1060039
+    field public static final int system_neutral_400 = 17170490; // 0x106003a
+    field public static final int system_neutral_50 = 17170486; // 0x1060036
+    field public static final int system_neutral_500 = 17170491; // 0x106003b
+    field public static final int system_neutral_600 = 17170492; // 0x106003c
+    field public static final int system_neutral_700 = 17170493; // 0x106003d
+    field public static final int system_neutral_800 = 17170494; // 0x106003e
+    field public static final int system_neutral_900 = 17170495; // 0x106003f
+    field public static final int system_primary_0 = 17170461; // 0x106001d
+    field public static final int system_primary_100 = 17170463; // 0x106001f
+    field public static final int system_primary_1000 = 17170472; // 0x1060028
+    field public static final int system_primary_200 = 17170464; // 0x1060020
+    field public static final int system_primary_300 = 17170465; // 0x1060021
+    field public static final int system_primary_400 = 17170466; // 0x1060022
+    field public static final int system_primary_50 = 17170462; // 0x106001e
+    field public static final int system_primary_500 = 17170467; // 0x1060023
+    field public static final int system_primary_600 = 17170468; // 0x1060024
+    field public static final int system_primary_700 = 17170469; // 0x1060025
+    field public static final int system_primary_800 = 17170470; // 0x1060026
+    field public static final int system_primary_900 = 17170471; // 0x1060027
+    field public static final int system_secondary_0 = 17170473; // 0x1060029
+    field public static final int system_secondary_100 = 17170475; // 0x106002b
+    field public static final int system_secondary_1000 = 17170484; // 0x1060034
+    field public static final int system_secondary_200 = 17170476; // 0x106002c
+    field public static final int system_secondary_300 = 17170477; // 0x106002d
+    field public static final int system_secondary_400 = 17170478; // 0x106002e
+    field public static final int system_secondary_50 = 17170474; // 0x106002a
+    field public static final int system_secondary_500 = 17170479; // 0x106002f
+    field public static final int system_secondary_600 = 17170480; // 0x1060030
+    field public static final int system_secondary_700 = 17170481; // 0x1060031
+    field public static final int system_secondary_800 = 17170482; // 0x1060032
+    field public static final int system_secondary_900 = 17170483; // 0x1060033
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -1741,6 +1765,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
   }
@@ -3865,6 +3892,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();
@@ -4301,16 +4329,17 @@
   }
 
   public class AlarmManager {
+    method public boolean canScheduleExactAlarms();
     method public void cancel(android.app.PendingIntent);
     method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
     method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
     method public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
+    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
     method public void setRepeating(int, long, long, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);
@@ -6063,6 +6092,13 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+    field public static final String EDIT_CONVERSATION = "convo";
+    field public static final String EDIT_IMPORTANCE = "importance";
+    field public static final String EDIT_LAUNCHER = "launcher";
+    field public static final String EDIT_LOCKED_DEVICE = "locked";
+    field public static final String EDIT_SOUND = "sound";
+    field public static final String EDIT_VIBRATION = "vibration";
+    field public static final String EDIT_ZEN = "dnd";
   }
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -6653,6 +6689,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);
@@ -6951,6 +6988,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);
@@ -7068,6 +7106,7 @@
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
     method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
+    method public boolean isNetworkSlicingEnabled();
     method public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
     method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -7078,7 +7117,9 @@
     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 @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
     method public void lockNow();
     method public void lockNow(int);
     method public int logoutUser(@NonNull android.content.ComponentName);
@@ -7138,6 +7179,7 @@
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
     method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
+    method public void setNetworkSlicingEnabled(boolean);
     method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
     method public void setOrganizationId(@NonNull String);
     method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7179,6 +7221,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);
@@ -8295,6 +8338,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);
@@ -8302,11 +8346,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 {
@@ -8359,6 +8405,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 {
@@ -8402,6 +8449,8 @@
     field public int initialKeyguardLayout;
     field public int initialLayout;
     field @Deprecated public String label;
+    field public int maxResizeHeight;
+    field public int maxResizeWidth;
     field public int minHeight;
     field public int minResizeHeight;
     field public int minResizeWidth;
@@ -8410,6 +8459,8 @@
     field @IdRes public int previewLayout;
     field public android.content.ComponentName provider;
     field public int resizeMode;
+    field public int targetCellHeight;
+    field public int targetCellWidth;
     field public int updatePeriodMillis;
     field public int widgetCategory;
     field public int widgetFeatures;
@@ -9820,6 +9871,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;
@@ -10226,12 +10278,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);
@@ -12168,6 +12223,7 @@
     method public void setAutoRevokePermissionsMode(boolean);
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
+    method public void setInstallScenario(int);
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12390,6 +12446,7 @@
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+    field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
     field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
     field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
     field public static final String FEATURE_LEANBACK = "android.software.leanback";
@@ -12496,6 +12553,10 @@
     field public static final int INSTALL_REASON_POLICY = 1; // 0x1
     field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
     field public static final int INSTALL_REASON_USER = 4; // 0x4
+    field public static final int INSTALL_SCENARIO_BULK = 2; // 0x2
+    field public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; // 0x3
+    field public static final int INSTALL_SCENARIO_DEFAULT = 0; // 0x0
+    field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1
     field public static final int MATCH_ALL = 131072; // 0x20000
     field public static final int MATCH_APEX = 1073741824; // 0x40000000
     field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
@@ -12778,6 +12839,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 {
@@ -16515,9 +16577,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 {
@@ -17643,6 +17709,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP;
@@ -20950,7 +21017,7 @@
     method @NonNull public String getDiagnosticInfo();
   }
 
-  public final class MediaCodec {
+  public final class MediaCodec implements android.media.metrics.PlaybackComponent {
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
     method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -20976,6 +21043,7 @@
     method @NonNull public android.media.MediaFormat getOutputFormat(int);
     method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
     method @Nullable public android.media.Image getOutputImage(int);
+    method public String getPlaybackId();
     method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
     method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
@@ -20991,6 +21059,7 @@
     method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
     method public void setOutputSurface(@NonNull android.view.Surface);
     method public void setParameters(@Nullable android.os.Bundle);
+    method public void setPlaybackId(@NonNull String);
     method public void setVideoScalingMode(int);
     method public void signalEndOfInputStream();
     method public void start();
@@ -21591,6 +21660,7 @@
     method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
     method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
     method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
+    method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
     method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
     method public static int getMaxSecurityLevel();
     method public int getMaxSessionCount();
@@ -21699,6 +21769,12 @@
     field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5
   }
 
+  public static class MediaDrm.LogMessage {
+    field @NonNull public final String message;
+    field public final int priority;
+    field public final long timestampMillis;
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method @NonNull public String getDiagnosticInfo();
   }
@@ -24047,13 +24123,213 @@
 
 package android.media.metrics {
 
+  public abstract class Event {
+    ctor protected Event(long);
+    method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis();
+  }
+
   public class MediaMetricsManager {
     method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession();
+    field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
+  }
+
+  public final class NetworkEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getNetworkType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.NetworkEvent> CREATOR;
+    field public static final int NETWORK_TYPE_2G = 4; // 0x4
+    field public static final int NETWORK_TYPE_3G = 5; // 0x5
+    field public static final int NETWORK_TYPE_4G = 6; // 0x6
+    field public static final int NETWORK_TYPE_5G_NSA = 7; // 0x7
+    field public static final int NETWORK_TYPE_5G_SA = 8; // 0x8
+    field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3
+    field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int NETWORK_TYPE_OTHER = 1; // 0x1
+    field public static final int NETWORK_TYPE_WIFI = 2; // 0x2
+  }
+
+  public static final class NetworkEvent.Builder {
+    ctor public NetworkEvent.Builder();
+    method @NonNull public android.media.metrics.NetworkEvent build();
+    method @NonNull public android.media.metrics.NetworkEvent.Builder setNetworkType(int);
+    method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
+  public interface PlaybackComponent {
+    method @NonNull public String getPlaybackId();
+    method public void setPlaybackId(@NonNull String);
+  }
+
+  public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getErrorCode();
+    method @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) public int getSubErrorCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackErrorEvent> CREATOR;
+    field public static final int ERROR_CODE_OTHER = 1; // 0x1
+    field public static final int ERROR_CODE_RUNTIME = 2; // 0x2
+    field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class PlaybackErrorEvent.Builder {
+    ctor public PlaybackErrorEvent.Builder();
+    method @NonNull public android.media.metrics.PlaybackErrorEvent build();
+    method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setErrorCode(int);
+    method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setException(@NonNull Exception);
+    method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setSubErrorCode(@IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
+  public final class PlaybackMetrics implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getAudioUnderrunCount();
+    method public int getContentType();
+    method public int getDrmType();
+    method @NonNull public long[] getExperimentIds();
+    method @IntRange(from=0xffffffff) public long getLocalBytesRead();
+    method @IntRange(from=0xffffffff) public long getMediaDurationMillis();
+    method @IntRange(from=0xffffffff) public long getNetworkBytesRead();
+    method @IntRange(from=0xffffffff) public long getNetworkTransferDurationMillis();
+    method public int getPlaybackType();
+    method @Nullable public String getPlayerName();
+    method @Nullable public String getPlayerVersion();
+    method public int getStreamSource();
+    method public int getStreamType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesDropped();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesPlayed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONTENT_TYPE_AD = 1; // 0x1
+    field public static final int CONTENT_TYPE_MAIN = 0; // 0x0
+    field public static final int CONTENT_TYPE_OTHER = 2; // 0x2
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackMetrics> CREATOR;
+    field public static final int DRM_TYPE_CLEARKEY = 6; // 0x6
+    field public static final int DRM_TYPE_NONE = 0; // 0x0
+    field public static final int DRM_TYPE_OTHER = 1; // 0x1
+    field public static final int DRM_TYPE_PLAY_READY = 2; // 0x2
+    field public static final int DRM_TYPE_WIDEVINE_L1 = 3; // 0x3
+    field public static final int DRM_TYPE_WIDEVINE_L3 = 4; // 0x4
+    field public static final int DRM_TYPE_WV_L3_FALLBACK = 5; // 0x5
+    field public static final int PLAYBACK_TYPE_LIVE = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_OTHER = 2; // 0x2
+    field public static final int PLAYBACK_TYPE_VOD = 0; // 0x0
+    field public static final int STREAM_SOURCE_DEVICE = 2; // 0x2
+    field public static final int STREAM_SOURCE_MIXED = 3; // 0x3
+    field public static final int STREAM_SOURCE_NETWORK = 1; // 0x1
+    field public static final int STREAM_SOURCE_UNKNOWN = 0; // 0x0
+    field public static final int STREAM_TYPE_DASH = 3; // 0x3
+    field public static final int STREAM_TYPE_HLS = 4; // 0x4
+    field public static final int STREAM_TYPE_OTHER = 1; // 0x1
+    field public static final int STREAM_TYPE_PROGRESSIVE = 2; // 0x2
+    field public static final int STREAM_TYPE_SS = 5; // 0x5
+    field public static final int STREAM_TYPE_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class PlaybackMetrics.Builder {
+    ctor public PlaybackMetrics.Builder();
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder addExperimentId(long);
+    method @NonNull public android.media.metrics.PlaybackMetrics build();
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setAudioUnderrunCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setContentType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setDrmType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setLocalBytesRead(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setMediaDurationMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkBytesRead(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkTransferDurationMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlaybackType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerName(@NonNull String);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerVersion(@NonNull String);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamSource(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamType(int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesDropped(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesPlayed(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
   }
 
   public final class PlaybackSession implements java.lang.AutoCloseable {
     method public void close();
     method @NonNull public String getId();
+    method public void reportNetworkEvent(@NonNull android.media.metrics.NetworkEvent);
+    method public void reportPlaybackErrorEvent(@NonNull android.media.metrics.PlaybackErrorEvent);
+    method public void reportPlaybackMetrics(@NonNull android.media.metrics.PlaybackMetrics);
+    method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent);
+    method public void reportTrackChangeEvent(@NonNull android.media.metrics.TrackChangeEvent);
+  }
+
+  public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackStateEvent> CREATOR;
+    field public static final int STATE_ABANDONED = 15; // 0xf
+    field public static final int STATE_BUFFERING = 6; // 0x6
+    field public static final int STATE_ENDED = 11; // 0xb
+    field public static final int STATE_FAILED = 13; // 0xd
+    field public static final int STATE_INTERRUPTED_BY_AD = 14; // 0xe
+    field public static final int STATE_JOINING_BACKGROUND = 1; // 0x1
+    field public static final int STATE_JOINING_FOREGROUND = 2; // 0x2
+    field public static final int STATE_NOT_STARTED = 0; // 0x0
+    field public static final int STATE_PAUSED = 4; // 0x4
+    field public static final int STATE_PAUSED_BUFFERING = 7; // 0x7
+    field public static final int STATE_PLAYING = 3; // 0x3
+    field public static final int STATE_SEEKING = 5; // 0x5
+    field public static final int STATE_STOPPED = 12; // 0xc
+    field public static final int STATE_SUPPRESSED = 9; // 0x9
+    field public static final int STATE_SUPPRESSED_BUFFERING = 10; // 0xa
+  }
+
+  public static final class PlaybackStateEvent.Builder {
+    ctor public PlaybackStateEvent.Builder();
+    method @NonNull public android.media.metrics.PlaybackStateEvent build();
+    method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setState(int);
+    method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
+  public final class TrackChangeEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    ctor public TrackChangeEvent(int, int, @Nullable String, @Nullable String, @Nullable String, int, long, int, @Nullable String, @Nullable String, int, int, int, int);
+    method public int describeContents();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getBitrate();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getChannelCount();
+    method @Nullable public String getCodecName();
+    method @Nullable public String getContainerMimeType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getHeight();
+    method @Nullable public String getLanguage();
+    method @Nullable public String getLanguageRegion();
+    method @Nullable public String getSampleMimeType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getSampleRate();
+    method public int getTrackChangeReason();
+    method public int getTrackState();
+    method public int getTrackType();
+    method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.TrackChangeEvent> CREATOR;
+    field public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4; // 0x4
+    field public static final int TRACK_CHANGE_REASON_INITIAL = 2; // 0x2
+    field public static final int TRACK_CHANGE_REASON_MANUAL = 3; // 0x3
+    field public static final int TRACK_CHANGE_REASON_OTHER = 1; // 0x1
+    field public static final int TRACK_CHANGE_REASON_UNKNOWN = 0; // 0x0
+    field public static final int TRACK_STATE_OFF = 0; // 0x0
+    field public static final int TRACK_STATE_ON = 1; // 0x1
+    field public static final int TRACK_TYPE_AUDIO = 0; // 0x0
+    field public static final int TRACK_TYPE_TEXT = 2; // 0x2
+    field public static final int TRACK_TYPE_VIDEO = 1; // 0x1
+  }
+
+  public static final class TrackChangeEvent.Builder {
+    ctor public TrackChangeEvent.Builder(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent build();
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setBitrate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setChannelCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setCodecName(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setContainerMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setHeight(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguage(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguageRegion(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleRate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackChangeReason(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackState(int);
+    method @NonNull public android.media.metrics.TrackChangeEvent.Builder setWidth(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
   }
 
 }
@@ -26694,6 +26970,46 @@
 
 }
 
+package android.net.vcn {
+
+  public final class VcnConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+  }
+
+  public static final class VcnConfig.Builder {
+    ctor public VcnConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+    method @NonNull public android.net.vcn.VcnConfig build();
+  }
+
+  public final class VcnGatewayConnectionConfig {
+    method @NonNull public int[] getExposedCapabilities();
+    method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu();
+    method @NonNull public int[] getRequiredUnderlyingCapabilities();
+    method @NonNull public long[] getRetryInterval();
+  }
+
+  public static final class VcnGatewayConnectionConfig.Builder {
+    ctor public VcnGatewayConnectionConfig.Builder();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]);
+  }
+
+  public class VcnManager {
+    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+  }
+
+}
+
 package android.nfc {
 
   public class FormatException extends java.lang.Exception {
@@ -31486,6 +31802,7 @@
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
+    method public boolean isUserForeground();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
@@ -34701,6 +35018,7 @@
     field public static final String EXTRA_AUTHORITIES = "authorities";
     field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
     field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
+    field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
     field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
@@ -35041,30 +35359,18 @@
 
   public static final class SimPhonebookContract.SimRecords {
     method @NonNull public static android.net.Uri getContentUri(int, int);
+    method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String);
     method @NonNull public static android.net.Uri getItemUri(int, int, int);
-    method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
     field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+    field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff
     field public static final String NAME = "name";
     field public static final String PHONE_NUMBER = "phone_number";
     field public static final String RECORD_NUMBER = "record_number";
     field public static final String SUBSCRIPTION_ID = "subscription_id";
   }
 
-  public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
-    ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
-    method public int describeContents();
-    method public int getEncodedLength();
-    method public int getMaxEncodedLength();
-    method @NonNull public String getName();
-    method @NonNull public String getSanitizedName();
-    method public boolean isSupportedCharacter(int);
-    method public boolean isValid();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
-  }
-
   public class SyncStateContract {
     ctor public SyncStateContract();
   }
@@ -36948,6 +37254,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method @Nullable public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method @Nullable public String getAttestKeyAlias();
     method public byte[] getAttestationChallenge();
     method @NonNull public String[] getBlockModes();
     method @NonNull public java.util.Date getCertificateNotAfter();
@@ -36982,6 +37289,7 @@
     ctor public KeyGenParameterSpec.Builder(@NonNull String, int);
     method @NonNull public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(@NonNull java.security.spec.AlgorithmParameterSpec);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestKeyAlias(@Nullable String);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(@NonNull java.util.Date);
@@ -37079,6 +37387,7 @@
     field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
     field public static final int ORIGIN_UNKNOWN = 4; // 0x4
     field public static final int PURPOSE_AGREE_KEY = 64; // 0x40
+    field public static final int PURPOSE_ATTEST_KEY = 128; // 0x80
     field public static final int PURPOSE_DECRYPT = 2; // 0x2
     field public static final int PURPOSE_ENCRYPT = 1; // 0x1
     field public static final int PURPOSE_SIGN = 4; // 0x4
@@ -39118,16 +39427,22 @@
   }
 
   public static class CallScreeningService.CallResponse {
+    method public int getCallComposerAttachmentsToShow();
     method public boolean getDisallowCall();
     method public boolean getRejectCall();
     method public boolean getSilenceCall();
     method public boolean getSkipCallLog();
     method public boolean getSkipNotification();
+    field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2
+    field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1
+    field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8
+    field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4
   }
 
   public static class CallScreeningService.CallResponse.Builder {
     ctor public CallScreeningService.CallResponse.Builder();
     method public android.telecom.CallScreeningService.CallResponse build();
+    method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int);
     method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean);
     method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean);
     method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean);
@@ -40277,6 +40592,7 @@
     field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+    field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
     field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
@@ -54628,6 +54944,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);
@@ -54642,6 +54959,7 @@
     method public void setAccessibilityTraversalAfter(@IdRes int, @IdRes int);
     method public void setAccessibilityTraversalBefore(@IdRes int, @IdRes int);
     method public void setBitmap(@IdRes int, String, android.graphics.Bitmap);
+    method public void setBlendMode(@IdRes int, @NonNull String, @Nullable android.graphics.BlendMode);
     method public void setBoolean(@IdRes int, String, boolean);
     method public void setBundle(@IdRes int, String, android.os.Bundle);
     method public void setByte(@IdRes int, String, byte);
@@ -55825,6 +56143,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 51d5139..de02d0b 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
   }
 
 }
@@ -211,6 +217,7 @@
     method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
     method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
     method public void teardownTestNetwork(@NonNull android.net.Network);
+    field public static final String TEST_TAP_PREFIX = "testtap";
   }
 
   public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b261d92..deff7b3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14,6 +14,7 @@
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
     field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+    field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
@@ -27,6 +28,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";
@@ -194,8 +196,10 @@
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
+    field public static final String READ_NETWORK_DEVICE_CONFIG = "android.permission.READ_NETWORK_DEVICE_CONFIG";
     field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
+    field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
     field public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
     field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
@@ -270,6 +274,7 @@
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+    field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -342,7 +347,9 @@
     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 = 17039401; // 0x1040029
+    field public static final int config_systemContacts = 17039403; // 0x104002b
     field public static final int config_systemGallery = 17039399; // 0x1040027
+    field public static final int config_systemShell = 17039402; // 0x104002a
   }
 
   public static final class R.style {
@@ -680,6 +687,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
   }
 
@@ -872,7 +880,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();
@@ -888,6 +895,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
+    method public boolean isNetworkSlicingEnabledForUser(@NonNull android.os.UserHandle);
     method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
@@ -1273,6 +1281,14 @@
 
 }
 
+package android.app.people {
+
+  public final class PeopleManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA) public boolean isConversation(@NonNull String, @NonNull String);
+  }
+
+}
+
 package android.app.prediction {
 
   public final class AppPredictionContext implements android.os.Parcelable {
@@ -1852,6 +1868,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();
@@ -2120,6 +2137,7 @@
     field public static final String OEM_LOCK_SERVICE = "oem_lock";
     field public static final String PERMISSION_SERVICE = "permission";
     field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
+    field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
     field public static final String ROLLBACK_SERVICE = "rollback";
     field public static final String SEARCH_UI_SERVICE = "search_ui";
     field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
@@ -2815,16 +2833,49 @@
 
 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 updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+    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.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int);
+    method @Deprecated @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
@@ -3507,6 +3558,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
@@ -7341,6 +7393,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
@@ -7355,6 +7408,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[]);
@@ -8528,7 +8582,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);
@@ -8598,6 +8652,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 {
@@ -8908,6 +8963,16 @@
 
 package android.permission {
 
+  public final class AdminPermissionControlParams implements android.os.Parcelable {
+    method public boolean canAdminGrantSensorsPermissions();
+    method public int describeContents();
+    method public int getGrantState();
+    method @NonNull public String getGranteePackageName();
+    method @NonNull public String getPermission();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.permission.AdminPermissionControlParams> CREATOR;
+  }
+
   public final class PermissionControllerManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
@@ -8939,7 +9004,8 @@
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
-    method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable);
@@ -9410,22 +9476,8 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
   }
 
-  public final class SimPhonebookContract {
-    method @NonNull public static String getEfUriPath(int);
-    field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
-  }
-
-  public static final class SimPhonebookContract.ElementaryFiles {
-    field public static final String EF_ADN_PATH_SEGMENT = "adn";
-    field public static final String EF_FDN_PATH_SEGMENT = "fdn";
-    field public static final String EF_SDN_PATH_SEGMENT = "sdn";
-    field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
-  }
-
   public static final class SimPhonebookContract.SimRecords {
-    field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
     field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
-    field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
   }
 
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
@@ -10719,7 +10771,7 @@
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Nullable public final String getTelecomCallId();
     method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method public final void resetConnectionTime();
     method public void setCallDirection(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
@@ -10896,7 +10948,7 @@
   }
 
   public final class RemoteConnection {
-    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
     method @Deprecated public void setAudioState(android.telecom.AudioState);
   }
 
@@ -11975,6 +12027,7 @@
     field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
     field public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; // 0x2
+    field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
     field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
     field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0
     field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
@@ -13409,8 +13462,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
     field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
     field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1
@@ -13689,10 +13742,16 @@
 package android.telephony.ims.stub {
 
   public interface CapabilityExchangeEventListener {
+    method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
     method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
     method public void onUnpublish() throws android.telephony.ims.ImsException;
   }
 
+  public static interface CapabilityExchangeEventListener.OptionsRequestCallback {
+    method public default void onRespondToCapabilityRequest(@NonNull android.telephony.ims.RcsContactUceCapability, boolean);
+    method public void onRespondToCapabilityRequestWithError(@IntRange(from=100, to=699) int, @NonNull String);
+  }
+
   public interface DelegateConnectionMessageCallback {
     method public void onMessageReceived(@NonNull android.telephony.ims.SipMessage);
     method public void onMessageSendFailure(@NonNull String, int);
@@ -13879,6 +13938,7 @@
   public class RcsCapabilityExchangeImplBase {
     ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
     method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
+    method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
     method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
     field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
     field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -13893,6 +13953,11 @@
     field public static final int COMMAND_CODE_SERVICE_UNKNOWN = 0; // 0x0
   }
 
+  public static interface RcsCapabilityExchangeImplBase.OptionsResponseCallback {
+    method public void onCommandError(int) throws android.telephony.ims.ImsException;
+    method public void onNetworkResponse(int, @NonNull String, @NonNull java.util.List<java.lang.String>) throws android.telephony.ims.ImsException;
+  }
+
   public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback {
     method public void onCommandError(int) throws android.telephony.ims.ImsException;
     method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
@@ -14176,10 +14241,10 @@
   }
 
   public final class RangingSession implements java.lang.AutoCloseable {
-    method public void close();
-    method public void reconfigure(@NonNull android.os.PersistableBundle);
-    method public void start(@NonNull android.os.PersistableBundle);
-    method public void stop();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void close();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void reconfigure(@NonNull android.os.PersistableBundle);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void start(@NonNull android.os.PersistableBundle);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void stop();
   }
 
   public static interface RangingSession.Callback {
@@ -14215,18 +14280,18 @@
   }
 
   public final class UwbManager {
-    method public long elapsedRealtimeResolutionNanos();
-    method public int getAngleOfArrivalSupport();
-    method public int getMaxRemoteDevicesPerInitiatorSession();
-    method public int getMaxRemoteDevicesPerResponderSession();
-    method public int getMaxSimultaneousSessions();
-    method @NonNull public android.os.PersistableBundle getSpecificationInfo();
-    method @NonNull public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
-    method @NonNull public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
-    method public boolean isRangingSupported();
-    method @NonNull public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
-    method public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
-    method public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getAngleOfArrivalSupport();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerInitiatorSession();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerResponderSession();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxSimultaneousSessions();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.PersistableBundle getSpecificationInfo();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public boolean isRangingSupported();
+    method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
+    method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; // 0x2
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; // 0x3
     field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; // 0x4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1be0fba..ff96f92 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -837,14 +837,16 @@
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
-    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+    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.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int);
+    method @Deprecated @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
@@ -1230,11 +1232,6 @@
     method public void forceResourceLost();
   }
 
-  public final class MediaCodec implements android.media.metrics.PlaybackComponent {
-    method public String getPlaybackId();
-    method public void setPlaybackId(@NonNull String);
-  }
-
   public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
@@ -1309,15 +1306,6 @@
 
 }
 
-package android.media.metrics {
-
-  public interface PlaybackComponent {
-    method @NonNull public String getPlaybackId();
-    method public void setPlaybackId(@NonNull String);
-  }
-
-}
-
 package android.media.tv {
 
   public final class TvInputManager {
@@ -2568,6 +2556,7 @@
 
   public final class InputMethodManager {
     method public int getDisplayId();
+    method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method public boolean isInputMethodPickerShown();
   }
 
@@ -2729,6 +2718,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();
@@ -2748,6 +2741,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/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/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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b85b186..1906ee4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -795,7 +795,8 @@
     // when adding one of these:
     //  - increment _NUM_OP
     //  - define an OPSTR_* constant (marked as @SystemApi)
-    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
+    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
+    //      sOpRestrictions, sOpAllowSystemRestrictionBypass
     //  - add descriptive strings to Settings/res/values/arrays.xml
     //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
 
@@ -1174,13 +1175,19 @@
      *
      * @hide
      */
-    // TODO: Add as AppProtoEnums
-    public static final int OP_RECORD_AUDIO_OUTPUT = 106;
+    public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+
+    /**
+     * App can schedule exact alarm to perform timing based background work
+     *
+     * @hide
+     */
+    public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
 
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 107;
+    public static final int _NUM_OP = 108;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1553,6 +1560,13 @@
      */
     public static final String OPSTR_RECORD_AUDIO_OUTPUT = "android:record_audio_output";
 
+    /**
+     * App can schedule exact alarm to perform timing based background work.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1633,6 +1647,7 @@
             OP_LOADER_USAGE_STATS,
             OP_MANAGE_ONGOING_CALLS,
             OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+            OP_SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -1751,6 +1766,7 @@
             OP_MANAGE_CREDENTIALS,              // MANAGE_CREDENTIALS
             OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             OP_RECORD_AUDIO_OUTPUT,             // RECORD_AUDIO_OUTPUT
+            OP_SCHEDULE_EXACT_ALARM,            // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -1864,6 +1880,7 @@
             OPSTR_MANAGE_CREDENTIALS,
             OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
             OPSTR_RECORD_AUDIO_OUTPUT,
+            OPSTR_SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -1978,6 +1995,7 @@
             "MANAGE_CREDENTIALS",
             "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
             "RECORD_AUDIO_OUTPUT",
+            "SCHEDULE_EXACT_ALARM",
     };
 
     /**
@@ -2093,6 +2111,7 @@
             null, // no permission for OP_MANAGE_CREDENTIALS
             Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
             null, // no permission for OP_RECORD_AUDIO_OUTPUT
+            Manifest.permission.SCHEDULE_EXACT_ALARM,
     };
 
     /**
@@ -2208,6 +2227,7 @@
             null, // MANAGE_CREDENTIALS
             null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             null, // RECORD_AUDIO_OUTPUT
+            null, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2322,6 +2342,7 @@
             null, // MANAGE_CREDENTIALS
             null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             null, // RECORD_AUDIO_OUTPUT
+            null, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2435,6 +2456,7 @@
             AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
             AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
+            AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
     };
 
     /**
@@ -2552,6 +2574,7 @@
             false, // MANAGE_CREDENTIALS
             true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
             false, // RECORD_AUDIO_OUTPUT
+            false, // SCHEDULE_EXACT_ALARM
     };
 
     /**
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 7410a1c..dfc105a 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -428,6 +428,13 @@
      */
     private IAppTraceRetriever mAppTraceRetriever;
 
+    /**
+     * ParcelFileDescriptor pointing to a native tombstone.
+     *
+     * @see #getTraceInputStream
+     */
+    private IParcelFileDescriptorRetriever mNativeTombstoneRetriever;
+
     /** @hide */
     @IntDef(prefix = { "REASON_" }, value = {
         REASON_UNKNOWN,
@@ -603,22 +610,38 @@
      * prior to the death of the process; typically it'll be available when
      * the reason is {@link #REASON_ANR}, though if the process gets an ANR
      * but recovers, and dies for another reason later, this trace will be included
-     * in the record of {@link ApplicationExitInfo} still.
+     * in the record of {@link ApplicationExitInfo} still. Beginning with API 31,
+     * tombstone traces will be returned for
+     * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
+     * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
+     * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
+     * overwritten by newer crashes (including from other applications), so this may still return
+     * null.
      *
      * @return The input stream to the traces that was taken by the system
      *         prior to the death of the process.
      */
     public @Nullable InputStream getTraceInputStream() throws IOException {
-        if (mAppTraceRetriever == null) {
+        if (mAppTraceRetriever == null && mNativeTombstoneRetriever == null) {
             return null;
         }
+
         try {
-            final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
-                    mPackageName, mPackageUid, mPid);
-            if (fd == null) {
-                return null;
+            if (mNativeTombstoneRetriever != null) {
+                final ParcelFileDescriptor pfd = mNativeTombstoneRetriever.getPfd();
+                if (pfd == null) {
+                    return null;
+                }
+
+                return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            } else {
+                final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor(
+                        mPackageName, mPackageUid, mPid);
+                if (fd == null) {
+                    return null;
+                }
+                return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
             }
-            return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd));
         } catch (RemoteException e) {
             return null;
         }
@@ -849,6 +872,15 @@
         mAppTraceRetriever = retriever;
     }
 
+    /**
+     * @see mNativeTombstoneRetriever
+     *
+     * @hide
+     */
+    public void setNativeTombstoneRetriever(final IParcelFileDescriptorRetriever retriever) {
+        mNativeTombstoneRetriever = retriever;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -878,6 +910,12 @@
         } else {
             dest.writeInt(0);
         }
+        if (mNativeTombstoneRetriever != null) {
+            dest.writeInt(1);
+            dest.writeStrongBinder(mNativeTombstoneRetriever.asBinder());
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /** @hide */
@@ -906,6 +944,7 @@
         mState = other.mState;
         mTraceFile = other.mTraceFile;
         mAppTraceRetriever = other.mAppTraceRetriever;
+        mNativeTombstoneRetriever = other.mNativeTombstoneRetriever;
     }
 
     private ApplicationExitInfo(@NonNull Parcel in) {
@@ -928,6 +967,10 @@
         if (in.readInt() == 1) {
             mAppTraceRetriever = IAppTraceRetriever.Stub.asInterface(in.readStrongBinder());
         }
+        if (in.readInt() == 1) {
+            mNativeTombstoneRetriever = IParcelFileDescriptorRetriever.Stub.asInterface(
+                    in.readStrongBinder());
+        }
     }
 
     public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
@@ -986,6 +1029,7 @@
         sb.append(" state=").append(ArrayUtils.isEmpty(mState)
                 ? "empty" : Integer.toString(mState.length) + " bytes");
         sb.append(" trace=").append(mTraceFile);
+
         return sb.toString();
     }
 
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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 96751db..033cffe 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -52,6 +52,7 @@
 
     private boolean mSharedElementTransitionStarted;
     private Activity mActivity;
+    private boolean mIsTaskRoot;
     private boolean mHasStopped;
     private boolean mIsCanceled;
     private ObjectAnimator mBackgroundAnimator;
@@ -252,7 +253,7 @@
                 cancel();
                 break;
             case MSG_ALLOW_RETURN_TRANSITION:
-                if (!mIsCanceled) {
+                if (!mIsCanceled && !mIsTaskRoot) {
                     mPendingExitNames = mAllSharedElementNames;
                 }
                 break;
@@ -343,6 +344,9 @@
         if (mActivity == null || decorView == null) {
             return;
         }
+
+        mIsTaskRoot = mActivity.isTaskRoot();
+
         if (!isCrossTask()) {
             mActivity.overridePendingTransition(0, 0);
         }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index f7097fa..cd84e56 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -551,7 +551,7 @@
 
         @Override
         public boolean isReturnTransitionAllowed() {
-            return !mActivity.isTopOfTask();
+            return true;
         }
 
         @Override
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/IParcelFileDescriptorRetriever.aidl b/core/java/android/app/IParcelFileDescriptorRetriever.aidl
new file mode 100644
index 0000000..7e808e7
--- /dev/null
+++ b/core/java/android/app/IParcelFileDescriptorRetriever.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * An interface used to lazily provide a ParcelFileDescriptor to apps.
+ *
+ * @hide
+ */
+interface IParcelFileDescriptorRetriever {
+    /**
+     * Retrieve the ParcelFileDescriptor.
+     */
+    ParcelFileDescriptor getPfd();
+}
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/Notification.java b/core/java/android/app/Notification.java
index b6fc47f..77daf8d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1604,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;
@@ -2315,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 {}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 323af821..685c222 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -80,6 +80,48 @@
     public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
 
     /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing sound, like a tone picker
+     * ({@link #setSound(Uri, AudioAttributes)}).
+     */
+    public static final String EDIT_SOUND = "sound";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing vibration ({@link #enableVibration(boolean)},
+     * {@link #setVibrationPattern(long[])}).
+     */
+    public static final String EDIT_VIBRATION = "vibration";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing importance ({@link #setImportance(int)}) and/or conversation
+     * priority.
+     */
+    public static final String EDIT_IMPORTANCE = "importance";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing behavior on devices that are locked or have a turned off
+     * display ({@link #setLockscreenVisibility(int)}, {@link #enableLights(boolean)},
+     * {@link #setLightColor(int)}).
+     */
+    public static final String EDIT_LOCKED_DEVICE = "locked";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
+     */
+    public static final String EDIT_ZEN = "dnd";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing conversation settings (demoting or restoring a channel to
+     * be a Conversation, changing bubble behavior, or setting the priority of a conversation).
+     */
+    public static final String EDIT_CONVERSATION = "convo";
+    /**
+     * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+     * that have to do with editing launcher behavior (showing badges)}.
+     */
+    public static final String EDIT_LAUNCHER = "launcher";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c047fc2..c47b546 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -185,6 +185,7 @@
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.scheduling.SchedulingFrameworkInitializer;
 import android.security.FileIntegrityManager;
 import android.security.IFileIntegrityService;
 import android.service.oemlock.IOemLockService;
@@ -1440,6 +1441,7 @@
             MediaFrameworkPlatformInitializer.registerServiceWrappers();
             MediaFrameworkInitializer.registerServiceWrappers();
             RoleFrameworkInitializer.registerServiceWrappers();
+            SchedulingFrameworkInitializer.registerServiceWrappers();
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 938ce0d..9019ddf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -343,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());
     }
 
     /**
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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff6f6a0..59e5144 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -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
@@ -1868,13 +1869,13 @@
     /**
      * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
      * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
-     * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
-     * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+     * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner or Profile Owner
+     * will no longer receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
      * There can be at most one app that has this delegation.
      * If another app already had delegated network logging access,
      * it will lose the delegation when a new app is delegated.
      *
-     * <p> Can only be granted by Device Owner.
+     * <p> Can only be granted by Device Owner or Profile Owner of a managed profile.
      */
     public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
 
@@ -9850,6 +9851,84 @@
     }
 
     /**
+     * Sets whether 5g slicing is enabled on the work profile.
+     *
+     * Slicing allows operators to virtually divide their networks in portions and use different
+     * portions for specific use cases; for example, a corporation can have a deal/agreement with
+     * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise
+     * use.
+     *
+     * By default, 5g slicing is enabled on the work profile on supported carriers and devices.
+     * Admins can explicitly disable it with this API.
+     *
+     * <p>This method can only be called by the profile owner of a managed profile.
+     *
+     * @param enabled whether 5g Slice should be enabled.
+     * @throws SecurityException if the caller is not the profile owner.
+     **/
+    public void setNetworkSlicingEnabled(boolean enabled) {
+        throwIfParentInstance("setNetworkSlicingEnabled");
+        if (mService != null) {
+            try {
+                mService.setNetworkSlicingEnabled(enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Indicates whether 5g slicing is enabled.
+     *
+     * <p>This method can be called by the profile owner of a managed profile.
+     *
+     * @return whether 5g Slice is enabled.
+     * @throws SecurityException if the caller is not the profile owner.
+     */
+    public boolean isNetworkSlicingEnabled() {
+        throwIfParentInstance("isNetworkSlicingEnabled");
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.isNetworkSlicingEnabled(myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates whether 5g slicing is enabled for specific user.
+     *
+     * This method can be called with permission
+     * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG} by the profile owner of
+     * a managed profile. And the caller must hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission if query for
+     * other users.
+     *
+     * @param userHandle indicates the user to query the state
+     * @return indicates whether 5g Slice is enabled.
+     * @throws SecurityException if the caller is not granted the permission
+     *         {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG}
+     *         and not profile owner of a managed profile, and not granted the permission
+     *         {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if query for
+     *         other users.
+     * @hide
+     */
+    @SystemApi
+    public boolean isNetworkSlicingEnabledForUser(@NonNull UserHandle userHandle) {
+        throwIfParentInstance("isNetworkSlicingEnabledForUser");
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.isNetworkSlicingEnabled(userHandle.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * This method is mostly deprecated.
      * Most of the settings that still have an effect have dedicated setter methods or user
      * restrictions. See individual settings for details.
@@ -11717,8 +11796,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:
@@ -11758,7 +11840,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
      */
@@ -11772,14 +11854,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");
@@ -11791,9 +11875,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.
      *
@@ -11805,11 +11894,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.
@@ -11817,8 +11906,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
      */
@@ -11937,11 +12027,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.
      *
@@ -13344,6 +13435,7 @@
             }
         }
     }
+
     /**
      * Returns true if the caller is running on a device where the admin can grant
      * permissions related to device sensors.
@@ -13357,24 +13449,109 @@
      */
     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;
+    }
+
+    /**
+     * Gets the list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @return list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+     *
+     * @throws SecurityException if the calling application is not a device owner
+     */
+    @NonNull
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        if (mService == null) return Collections.emptyList();
+
+        try {
+            return mService.listForegroundAffiliatedUsers();
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 032cf24..8a87b16 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -267,6 +267,9 @@
     void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
     boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
 
+    void setNetworkSlicingEnabled(in boolean enabled);
+    boolean isNetworkSlicingEnabled(int userHandle);
+
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
@@ -502,4 +505,11 @@
 
     void resetDefaultCrossProfileIntentFilters(int userId);
     boolean canAdminGrantSensorsPermissionsForUser(int userId);
+
+    void setUsbDataSignalingEnabled(String callerPackage, boolean enabled);
+    boolean isUsbDataSignalingEnabled(String callerPackage);
+    boolean isUsbDataSignalingEnabledForUser(int userId);
+    boolean canUsbDataSignalingBeDisabled();
+
+    List<UserHandle> listForegroundAffiliatedUsers();
 }
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/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/people/IConversationListener.aidl b/core/java/android/app/people/IConversationListener.aidl
new file mode 100644
index 0000000..7cbd66d
--- /dev/null
+++ b/core/java/android/app/people/IConversationListener.aidl
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import android.app.people.ConversationChannel;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Interface for PeopleManager#ConversationListener.
+ *
+ * @hide
+ */
+oneway interface IConversationListener
+{
+    void onConversationUpdate(in ConversationChannel conversation);
+}
\ No newline at end of file
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index ebe9f60..496ca82 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.people.ConversationStatus;
 import android.app.people.ConversationChannel;
+import android.app.people.IConversationListener;
 import android.content.pm.ParceledListSlice;
 import android.net.Uri;
 import android.os.IBinder;
@@ -49,6 +50,9 @@
     /** Removes all the recent conversations and uncaches their cached shortcuts. */
     void removeAllRecentConversations();
 
+    /** Returns whether the shortcutId is backed by a Conversation in People Service. */
+    boolean isConversation(in String packageName, int userId, in String shortcutId);
+
     /**
      * Returns the last interaction with the specified conversation. If the
      * conversation can't be found or no interactions have been recorded, returns 0L.
@@ -59,4 +63,6 @@
     void clearStatus(in String packageName, int userId, in String conversationId, in String statusId);
     void clearStatuses(in String packageName, int userId, in String conversationId);
     ParceledListSlice getStatuses(in String packageName, int userId, in String conversationId);
+    void registerConversationListener(in String packageName, int userId, in String shortcutId, in IConversationListener callback);
+    void unregisterConversationListener(in IConversationListener callback);
 }
diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java
index de7ba62..108437e 100644
--- a/core/java/android/app/people/PeopleManager.java
+++ b/core/java/android/app/people/PeopleManager.java
@@ -16,19 +16,29 @@
 
 package android.app.people;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Pair;
+import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * This class allows interaction with conversation and people data.
@@ -38,11 +48,18 @@
 
     private static final String LOG_TAG = PeopleManager.class.getSimpleName();
 
-    @NonNull
-    private final Context mContext;
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public Map<ConversationListener, Pair<Executor, IConversationListener>>
+            mConversationListeners = new HashMap<>();
 
     @NonNull
-    private final IPeopleManager mService;
+    private Context mContext;
+
+    @NonNull
+    private IPeopleManager mService;
 
     /**
      * @hide
@@ -53,6 +70,41 @@
                 Context.PEOPLE_SERVICE));
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public PeopleManager(@NonNull Context context, IPeopleManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns whether a shortcut has a conversation associated.
+     *
+     * <p>Requires android.permission.READ_PEOPLE_DATA permission.
+     *
+     * <p>This method may return different results for the same shortcut over time, as an app adopts
+     * conversation features or if a user hasn't communicated with the conversation associated to
+     * the shortcut in a while, so the result should not be stored and relied on indefinitely by
+     * clients.
+     *
+     * @param packageName name of the package the conversation is part of
+     * @param shortcutId  the shortcut id backing the conversation
+     * @return whether the {@shortcutId} is backed by a Conversation.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA)
+    public boolean isConversation(@NonNull String packageName, @NonNull String shortcutId) {
+        Preconditions.checkStringNotEmpty(packageName);
+        Preconditions.checkStringNotEmpty(shortcutId);
+        try {
+            return mService.isConversation(packageName, mContext.getUserId(), shortcutId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Sets or updates a {@link ConversationStatus} for a conversation.
@@ -65,8 +117,7 @@
      *
      * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
      *                       conversation that has an active status
-     * @param status the current status for the given conversation
-     *
+     * @param status         the current status for the given conversation
      * @return whether the role is available in the system
      */
     public void addOrUpdateStatus(@NonNull String conversationId,
@@ -86,8 +137,8 @@
      *
      * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
      *                       conversation that has an active status
-     * @param statusId the {@link ConversationStatus#getId() id} of a published status for the given
-     *                 conversation
+     * @param statusId       the {@link ConversationStatus#getId() id} of a published status for the
+     *                       given conversation
      */
     public void clearStatus(@NonNull String conversationId, @NonNull String statusId) {
         Preconditions.checkStringNotEmpty(conversationId);
@@ -126,7 +177,7 @@
         try {
             final ParceledListSlice<ConversationStatus> parceledList
                     = mService.getStatuses(
-                            mContext.getPackageName(), mContext.getUserId(), conversationId);
+                    mContext.getPackageName(), mContext.getUserId(), conversationId);
             if (parceledList != null) {
                 return parceledList.getList();
             }
@@ -135,4 +186,103 @@
         }
         return new ArrayList<>();
     }
+
+    /**
+     * Listeners for conversation changes.
+     *
+     * @hide
+     */
+    public interface ConversationListener {
+        /**
+         * Triggers when the conversation registered for a listener has been updated.
+         *
+         * @param conversation The conversation with modified data
+         * @see IPeopleManager#registerConversationListener(String, int, String,
+         * android.app.people.ConversationListener)
+         *
+         * <p>Only system root and SysUI have access to register the listener.
+         */
+        default void onConversationUpdate(@NonNull ConversationChannel conversation) {
+        }
+    }
+
+    /**
+     * Register a listener to watch for changes to the conversation identified by {@code
+     * packageName}, {@code userId}, and {@code shortcutId}.
+     *
+     * @param packageName The package name to match and filter the conversation to send updates for.
+     * @param userId      The user ID to match and filter the conversation to send updates for.
+     * @param shortcutId  The shortcut ID to match and filter the conversation to send updates for.
+     * @param listener    The listener to register to receive conversation updates.
+     * @param executor    {@link Executor} to handle the listeners. To dispatch listeners to the
+     *                    main thread of your application, you can use
+     *                    {@link android.content.Context#getMainExecutor()}.
+     * @hide
+     */
+    public void registerConversationListener(String packageName, int userId, String shortcutId,
+            ConversationListener listener, Executor executor) {
+        requireNonNull(listener, "Listener cannot be null");
+        requireNonNull(packageName, "Package name cannot be null");
+        requireNonNull(shortcutId, "Shortcut ID cannot be null");
+        synchronized (mConversationListeners) {
+            IConversationListener proxy = (IConversationListener) new ConversationListenerProxy(
+                    executor, listener);
+            try {
+                mService.registerConversationListener(
+                        packageName, userId, shortcutId, proxy);
+                mConversationListeners.put(listener,
+                        new Pair<>(executor, proxy));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the listener previously registered to watch conversation changes.
+     *
+     * @param listener The listener to register to receive conversation updates.
+     * @hide
+     */
+    public void unregisterConversationListener(
+            ConversationListener listener) {
+        requireNonNull(listener, "Listener cannot be null");
+
+        synchronized (mConversationListeners) {
+            if (mConversationListeners.containsKey(listener)) {
+                IConversationListener proxy = mConversationListeners.remove(listener).second;
+                try {
+                    mService.unregisterConversationListener(proxy);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Listener proxy class for {@link ConversationListener}
+     *
+     * @hide
+     */
+    private static class ConversationListenerProxy extends
+            IConversationListener.Stub {
+        private final Executor mExecutor;
+        private final ConversationListener mListener;
+
+        ConversationListenerProxy(Executor executor, ConversationListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onConversationUpdate(@NonNull ConversationChannel conversation) {
+            if (mListener == null || mExecutor == null) {
+                // Binder is dead.
+                Slog.e(LOG_TAG, "Binder is dead");
+                return;
+            }
+            mExecutor.execute(() -> mListener.onConversationUpdate(conversation));
+        }
+    }
 }
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/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
index 439d851..f17b044 100644
--- a/core/java/android/app/smartspace/SmartspaceAction.java
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -89,7 +89,7 @@
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
         mIntent = in.readTypedObject(Intent.CREATOR);
         mUserHandle = in.readTypedObject(UserHandle.CREATOR);
-        mExtras = in.readTypedObject(Bundle.CREATOR);
+        mExtras = in.readBundle();
     }
 
     private SmartspaceAction(
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/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 42214d0..d893a5e 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -179,6 +179,44 @@
     public int minResizeHeight;
 
     /**
+     * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+     * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
+     *
+     * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int maxResizeWidth;
+
+    /**
+     * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+     * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
+     *
+     * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int maxResizeHeight;
+
+    /**
+     * The default width of a widget when added to a host, in units of launcher grid cells.
+     *
+     * <p>This field corresponds to the <code>android:targetCellWidth</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int targetCellWidth;
+
+    /**
+     * The default height of a widget when added to a host, in units of launcher grid cells.
+     *
+     * <p>This field corresponds to the <code>android:targetCellHeight</code> attribute in the
+     * AppWidget meta-data file.
+     */
+    @SuppressLint("MutableBareField")
+    public int targetCellHeight;
+
+    /**
      * How often, in milliseconds, that this AppWidget wants to be updated.
      * The AppWidget manager may place a limit on how often a AppWidget is updated.
      *
@@ -330,6 +368,10 @@
         this.minHeight = in.readInt();
         this.minResizeWidth = in.readInt();
         this.minResizeHeight = in.readInt();
+        this.maxResizeWidth = in.readInt();
+        this.maxResizeHeight = in.readInt();
+        this.targetCellWidth = in.readInt();
+        this.targetCellHeight = in.readInt();
         this.updatePeriodMillis = in.readInt();
         this.initialLayout = in.readInt();
         this.initialKeyguardLayout = in.readInt();
@@ -440,6 +482,10 @@
         out.writeInt(this.minHeight);
         out.writeInt(this.minResizeWidth);
         out.writeInt(this.minResizeHeight);
+        out.writeInt(this.maxResizeWidth);
+        out.writeInt(this.maxResizeHeight);
+        out.writeInt(this.targetCellWidth);
+        out.writeInt(this.targetCellHeight);
         out.writeInt(this.updatePeriodMillis);
         out.writeInt(this.initialLayout);
         out.writeInt(this.initialKeyguardLayout);
@@ -463,8 +509,12 @@
         that.provider = this.provider == null ? null : this.provider.clone();
         that.minWidth = this.minWidth;
         that.minHeight = this.minHeight;
-        that.minResizeWidth = this.minResizeHeight;
+        that.minResizeWidth = this.minResizeWidth;
         that.minResizeHeight = this.minResizeHeight;
+        that.maxResizeWidth = this.maxResizeWidth;
+        that.maxResizeHeight = this.maxResizeHeight;
+        that.targetCellWidth = this.targetCellWidth;
+        that.targetCellHeight = this.targetCellHeight;
         that.updatePeriodMillis = this.updatePeriodMillis;
         that.initialLayout = this.initialLayout;
         that.initialKeyguardLayout = this.initialKeyguardLayout;
@@ -512,6 +562,8 @@
         minHeight = TypedValue.complexToDimensionPixelSize(minHeight, displayMetrics);
         minResizeWidth = TypedValue.complexToDimensionPixelSize(minResizeWidth, displayMetrics);
         minResizeHeight = TypedValue.complexToDimensionPixelSize(minResizeHeight, displayMetrics);
+        maxResizeWidth = TypedValue.complexToDimensionPixelSize(maxResizeWidth, displayMetrics);
+        maxResizeHeight = TypedValue.complexToDimensionPixelSize(maxResizeHeight, displayMetrics);
     }
 
     /**
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/Context.java b/core/java/android/content/Context.java
index 025d777..5166943 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;
 
 /**
@@ -3520,6 +3521,7 @@
             APPWIDGET_SERVICE,
             //@hide: VOICE_INTERACTION_MANAGER_SERVICE,
             //@hide: BACKUP_SERVICE,
+            REBOOT_READINESS_SERVICE,
             ROLLBACK_SERVICE,
             DROPBOX_SERVICE,
             //@hide: DEVICE_IDLE_CONTROLLER,
@@ -4742,6 +4744,17 @@
     public static final String ROLLBACK_SERVICE = "rollback";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.app.scheduling.RebootReadinessManagerService} for communicating
+     * with the reboot readiness detector.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    @SystemApi
+    public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.os.DropBoxManager} instance for recording
      * diagnostic logs.
@@ -5725,6 +5738,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
@@ -5755,6 +5794,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
@@ -5775,6 +5840,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/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/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0c0e402..80fecc1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -167,10 +167,17 @@
      */
     public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
 
+    /**
+     * Cache shortcuts which are used in People Tile.
+     * @hide
+     */
+    public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
+
     /** @hide */
     @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
             FLAG_CACHE_NOTIFICATION_SHORTCUTS,
             FLAG_CACHE_BUBBLE_SHORTCUTS,
+            FLAG_CACHE_PEOPLE_TILE_SHORTCUTS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutCacheFlags {}
@@ -1179,6 +1186,7 @@
      * <ul>
      *     <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
      *     <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+     *     <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
      * </ul>
      * @throws IllegalStateException when the user is locked, or when the {@code user} user
      * is locked or not running.
@@ -1209,6 +1217,7 @@
      * <ul>
      *     <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
      *     <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+     *     <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
      * </ul>
      * @throws IllegalStateException when the user is locked, or when the {@code user} user
      * is locked or not running.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f9980bc..567501c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2065,6 +2065,13 @@
             this.forceQueryableOverride = true;
         }
 
+        /**
+         * Sets the install scenario for this session, which describes the expected user journey.
+         */
+        public void setInstallScenario(@InstallScenario int installScenario) {
+            this.installScenario = installScenario;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -2224,7 +2231,7 @@
         /** {@hide} */
         public @InstallReason int installReason;
         /** {@hide} */
-        public @InstallReason int installScenario;
+        public @InstallScenario int installScenario;
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long sizeBytes;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a3c3500..fdb00c6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1356,15 +1356,11 @@
 
     /**
      * A value to indicate the lack of CUJ information, disabling all installation scenario logic.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_DEFAULT = 0;
 
     /**
      * Installation scenario providing the fastest “install button to launch" experience possible.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_FAST = 1;
 
@@ -1381,8 +1377,6 @@
      * less optimized applications.  The device state (e.g. memory usage or battery status) should
      * not be considered when making this decision as those factors are taken into account by the
      * Package Manager when acting on the installation scenario.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK = 2;
 
@@ -1393,8 +1387,6 @@
      * operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be.
      *
      * See the comments for INSTALL_SCENARIO_BULK for more information.
-     *
-     * @hide
      */
     public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3;
 
@@ -3677,6 +3669,15 @@
     public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
             "android.hardware.keystore.limited_use_key";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * a Keystore implementation that can create application-specific attestation keys.
+     * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+            "android.hardware.keystore.app_attest_key";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 522f4ca..5f80ba1 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -129,6 +129,12 @@
     /** @hide */
     public static final int FLAG_HAS_ICON_URI = 1 << 15;
 
+    /**
+     * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+     *  need to be aware of the outside world. Replace this with a more extensible solution.
+     * @hide
+     */
+    public static final int FLAG_CACHED_PEOPLE_TILE = 1 << 29;
 
     /**
      * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
@@ -138,7 +144,8 @@
     public static final int FLAG_CACHED_BUBBLES = 1 << 30;
 
     /** @hide */
-    public static final int FLAG_CACHED_ALL = FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES;
+    public static final int FLAG_CACHED_ALL =
+            FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
 
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -159,6 +166,7 @@
             FLAG_HAS_ICON_URI,
             FLAG_CACHED_NOTIFICATIONS,
             FLAG_CACHED_BUBBLES,
+            FLAG_CACHED_PEOPLE_TILE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ShortcutFlags {}
@@ -434,6 +442,8 @@
 
     private int mDisabledReason;
 
+    private int mStartingThemeResId;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -462,6 +472,7 @@
         mLocusId = b.mLocusId;
 
         updateTimestamp();
+        mStartingThemeResId = b.mStartingThemeResId;
     }
 
     /**
@@ -608,6 +619,7 @@
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
         }
+        mStartingThemeResId = source.mStartingThemeResId;
     }
 
     /**
@@ -931,6 +943,9 @@
         if (source.mLocusId != null) {
             mLocusId = source.mLocusId;
         }
+        if (source.mStartingThemeResId != 0) {
+            mStartingThemeResId = source.mStartingThemeResId;
+        }
     }
 
     /**
@@ -1000,6 +1015,8 @@
 
         private LocusId mLocusId;
 
+        private int mStartingThemeResId;
+
         /**
          * Old style constructor.
          * @hide
@@ -1102,6 +1119,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 +1446,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 +2172,7 @@
         mPersons = source.readParcelableArray(cl, Person.class);
         mLocusId = source.readParcelable(cl);
         mIconUri = source.readString8();
+        mStartingThemeResId = source.readInt();
     }
 
     @Override
@@ -2189,6 +2224,7 @@
         dest.writeParcelableArray(mPersons, flags);
         dest.writeParcelable(mLocusId, flags);
         dest.writeString8(mIconUri);
+        dest.writeInt(mStartingThemeResId);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2345,6 +2381,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 +2472,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 +2501,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/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/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..e512cf1 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.
@@ -205,42 +208,40 @@
     }
 
     /**
-     * Update system installed font file.
+     * Update a system installed font file.
      *
      * <p>
-     * To protect devices, system font updater relies on the Linux Kernel feature called fs-verity.
-     * If the device is not ready for fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be
+     * To protect devices, system font updater relies on a Linux Kernel feature called fs-verity.
+     * If the device does not support fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be
      * returned.
      *
-     * Android only accepts OpenType compliant font files. If other font files are provided,
+     * <p>Android only accepts OpenType compliant font files. If other font files are provided,
      * {@link #RESULT_ERROR_INVALID_FONT_FILE} will be returned.
      *
-     * The font file to be updated is identified by PostScript name stored in name table. If the
-     * font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} will be
-     * returned.
+     * <p>The font file to be updated is identified by PostScript name stored in the name table. If
+     * the font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME}
+     * will be returned.
      *
-     * The entire font file is verified with the given signature for the system installed
-     * certificate. If the system cannot verify the font contents,
+     * <p>The entire font file is verified with the given signature using system installed
+     * certificates. If the system cannot verify the font file contents,
      * {@link #RESULT_ERROR_VERIFICATION_FAILURE} will be returned.
      *
-     * The font file must have newer or equal revision number in the head table. In other words, the
-     * downgrading font file is not allowed. If the older font file is provided,
+     * <p>The font file must have a newer revision number in the head table. In other words, it is
+     * not allowed to downgrade a font file. If an older font file is provided,
      * {@link #RESULT_ERROR_DOWNGRADING} will be returned.
      *
-     * The caller must specify the base config version for keeping consist system configuration. If
-     * the system configuration is updated for some reason between you get config with
-     * {@link #getFontConfig()} and calling this method, {@link #RESULT_ERROR_VERSION_MISMATCH} will
-     * be returned. Get the latest font configuration by calling {@link #getFontConfig()} again and
-     * try with the latest config version again.
+     * <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 pfd A file descriptor of the font file.
-     * @param signature A PKCS#7 detached signature for verifying entire font files.
-     * @param baseVersion A base config version to be updated. You can get latest config version by
-     *                    {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If the
-     *                    system has newer config version, the update will fail with
-     *                    {@link #RESULT_ERROR_VERSION_MISMATCH}. Try to get the latest config and
-     *                    try update again.
-     * @return result code.
+     * @param request A {@link FontFileUpdateRequest} 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()
@@ -253,18 +254,88 @@
      * @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 FontFileUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+        try {
+            return mIFontManager.updateFontFile(new FontUpdateRequest(
+                    request.getParcelFileDescriptor(), request.getSignature()), baseVersion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #updateFontFile(FontFileUpdateRequest, int)}
+     */
+    // TODO: Remove this API before Developer Preview 3.
+    @Deprecated
+    @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
             @NonNull ParcelFileDescriptor pfd,
             @NonNull byte[] signature,
             @IntRange(from = 0) int baseVersion
     ) {
+        return updateFontFile(new FontFileUpdateRequest(pfd, signature), baseVersion);
+    }
+
+
+    /**
+     * 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.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+            return mIFontManager.updateFontFamily(requests, baseVersion);
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to call updateFont API", e);
-            return RESULT_ERROR_REMOTE_EXCEPTION;
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index f551d6a..b79c8f62 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -19,13 +19,17 @@
 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.
@@ -80,6 +84,26 @@
         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());
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 376503e..1ffd18f 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,11 +16,18 @@
 
 package android.hardware;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -54,6 +61,19 @@
     private static final boolean DEBUG_DYNAMIC_SENSOR = true;
     private static final int MIN_DIRECT_CHANNEL_BUFFER_SIZE = 104;
     private static final int MAX_LISTENER_COUNT = 128;
+    private static final int CAPPED_SAMPLING_PERIOD_US = 5000;
+    private static final int CAPPED_SAMPLING_RATE_LEVEL = SensorDirectChannel.RATE_NORMAL;
+
+    private static final String HIGH_SAMPLING_RATE_SENSORS_PERMISSION =
+                                        "android.permisison.HIGH_SAMPLING_RATE_SENSORS";
+    /**
+     * For apps targeting S and above, a SecurityException is thrown when they do not have
+     * HIGH_SAMPLING_RATE_SENSORS permission, run in debug mode, and request sampling rates that
+     * are faster than 200 Hz.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    static final long CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION = 136069189L;
 
     private static native void nativeClassInit();
     private static native long nativeCreate(String opPackageName);
@@ -98,6 +118,8 @@
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
     private final int mTargetSdkLevel;
+    private final boolean mIsPackageDebuggable;
+    private final boolean mHasHighSamplingRateSensorsPermission;
     private final Context mContext;
     private final long mNativeInstance;
 
@@ -111,9 +133,16 @@
         }
 
         mMainLooper = mainLooper;
-        mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion;
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        mTargetSdkLevel = appInfo.targetSdkVersion;
         mContext = context;
         mNativeInstance = nativeCreate(context.getOpPackageName());
+        mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+        PackageManager packageManager = context.getPackageManager();
+        mHasHighSamplingRateSensorsPermission =
+                (PERMISSION_GRANTED == packageManager.checkPermission(
+                        HIGH_SAMPLING_RATE_SENSORS_PERMISSION,
+                        appInfo.packageName));
 
         // initialize the sensor list
         for (int index = 0;; ++index) {
@@ -542,10 +571,18 @@
         }
 
         int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
+        if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
+                && rate > CAPPED_SAMPLING_RATE_LEVEL
+                && mIsPackageDebuggable
+                && !mHasHighSamplingRateSensorsPermission) {
+            Compatibility.reportChange(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION);
+            throw new SecurityException("To use the sampling rate level " + rate
+                    + ", app needs to declare the normal permission"
+                    + " HIGH_SAMPLING_RATE_SENSORS.");
+        }
 
         int ret = nativeConfigDirectChannel(
                 mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
-
         if (rate == SensorDirectChannel.RATE_STOP) {
             return (ret == 0) ? 1 : 0;
         } else {
@@ -745,6 +782,15 @@
                 Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
             if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
+            if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
+                    && rateUs < CAPPED_SAMPLING_PERIOD_US
+                    && mManager.mIsPackageDebuggable
+                    && !mManager.mHasHighSamplingRateSensorsPermission) {
+                Compatibility.reportChange(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION);
+                throw new SecurityException("To use the sampling rate of " + rateUs
+                        + " microseconds, app needs to declare the normal permission"
+                        + " HIGH_SAMPLING_RATE_SENSORS.");
+            }
             return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
                     maxBatchReportLatencyUs);
         }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 5f5697a..5b28e00 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -237,7 +237,8 @@
     public BiometricTestSession createTestSession(int sensorId) {
         try {
             return new BiometricTestSession(mContext, sensorId,
-                    mService.createTestSession(sensorId, mContext.getOpPackageName()));
+                    (context, sensorId1, callback) -> mService
+                            .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -390,7 +391,6 @@
      * in Keystore land as SIDs, and are used during key generation.
      * @hide
      */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public long[] getAuthenticatorIds() {
         if (mService != null) {
             try {
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 1c35608..ff1a17e 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.TEST_BIOMETRIC;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -27,6 +28,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
  * {@link android.hardware.fingerprint.FingerprintManager}.
@@ -36,22 +40,58 @@
 public class BiometricTestSession implements AutoCloseable {
     private static final String TAG = "BiometricTestSession";
 
+    /**
+     * @hide
+     */
+    public interface TestSessionProvider {
+        @NonNull
+        ITestSession createTestSession(@NonNull Context context, int sensorId,
+                @NonNull ITestSessionCallback callback) throws RemoteException;
+    }
+
     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.
-    private final ArraySet<Integer> mTestedUsers;
+    @NonNull private final ArraySet<Integer> mTestedUsers;
+
+    // Track the users currently cleaning up, and provide a latch that gets notified when all
+    // users have finished cleaning up. This is an imperfect system, as there can technically be
+    // multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's
+    // unique ID, but it's complicated to plumb it through. This should be fine for now.
+    @Nullable private CountDownLatch mCloseLatch;
+    @NonNull private final ArraySet<Integer> mUsersCleaningUp;
+
+    private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() {
+        @Override
+        public void onCleanupStarted(int userId) {
+            Log.d(TAG, "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId);
+        }
+
+        @Override
+        public void onCleanupFinished(int userId) {
+            Log.d(TAG, "onCleanupFinished, sensor: " + mSensorId
+                    + ", userId: " + userId
+                    + ", remaining users: " + mUsersCleaningUp.size());
+            mUsersCleaningUp.remove(userId);
+
+            if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) {
+                mCloseLatch.countDown();
+            }
+        }
+    };
 
     /**
      * @hide
      */
     public BiometricTestSession(@NonNull Context context, int sensorId,
-            @NonNull ITestSession testSession) {
+            @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
         mContext = context;
         mSensorId = sensorId;
-        mTestSession = testSession;
+        mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback);
         mTestedUsers = new ArraySet<>();
+        mUsersCleaningUp = new ArraySet<>();
         setTestHalEnabled(true);
     }
 
@@ -176,6 +216,11 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public void cleanupInternalState(int userId) {
         try {
+            if (mUsersCleaningUp.contains(userId)) {
+                Log.w(TAG, "Cleanup already in progress for user: " + userId);
+            }
+
+            mUsersCleaningUp.add(userId);
             mTestSession.cleanupInternalState(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -185,12 +230,24 @@
     @Override
     @RequiresPermission(TEST_BIOMETRIC)
     public void close() {
-        // Disable the test HAL first, so that enumerate is run on the real HAL, which should have
-        // no enrollments. Test-only framework enrollments will be deleted.
-        setTestHalEnabled(false);
+        // Cleanup can be performed using the test HAL, since it always responds to enumerate with
+        // zero enrollments.
+        if (!mTestedUsers.isEmpty()) {
+            mCloseLatch = new CountDownLatch(1);
+            for (int user : mTestedUsers) {
+                cleanupInternalState(user);
+            }
 
-        for (int user : mTestedUsers) {
-            cleanupInternalState(user);
+            try {
+                Log.d(TAG, "Awaiting latch...");
+                mCloseLatch.await(10, TimeUnit.SECONDS);
+                Log.d(TAG, "Finished awaiting");
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Latch interrupted", e);
+            }
         }
+
+        // Disable the test HAL after the sensor becomes idle.
+        setTestHalEnabled(false);
     }
 }
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 0dfd5db..d8c9dbc 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 
@@ -32,7 +33,7 @@
  */
 interface IAuthService {
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties for all biometric sensors
     List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index c854ac98..7639c5d 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
@@ -32,7 +33,7 @@
 interface IBiometricAuthenticator {
 
     // Creates a test session
-    ITestSession createTestSession(String opPackageName);
+    ITestSession createTestSession(ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties
     SensorPropertiesInternal getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index a14a910..2433186 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 
@@ -30,7 +31,7 @@
  */
 interface IBiometricService {
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Retrieve static sensor properties for all biometric sensors
     List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index fa7a62c..f8395a1 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -18,7 +18,7 @@
 import android.hardware.biometrics.SensorPropertiesInternal;
 
 /**
- * A test service for FingerprintManager and BiometricPrompt.
+ * A test service for FingerprintManager and BiometricManager.
  * @hide
  */
 interface ITestSession {
diff --git a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
new file mode 100644
index 0000000..3d9517f
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.hardware.biometrics;
+
+/**
+ * ITestSession callback for FingerprintManager and BiometricManager.
+ * @hide
+ */
+interface ITestSessionCallback {
+    void onCleanupStarted(int userId);
+    void onCleanupFinished(int userId);
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 0f595b7..16ab900 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2908,6 +2908,29 @@
             new Key<int[]>("android.scaler.availableRotateAndCropModes", int[].class);
 
     /**
+     * <p>Default YUV/PRIVATE size to use for requesting secure image buffers.</p>
+     * <p>This entry lists the default size supported in the secure camera mode. This entry is
+     * optional on devices support the SECURE_IMAGE_DATA capability. This entry will be null
+     * if the camera device does not list SECURE_IMAGE_DATA capability.</p>
+     * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
+     * to be supported by the camera HAL in the secure camera mode. Any other format or
+     * resolutions might not be supported. Use
+     * {@link CameraDevice#isSessionConfigurationSupported }
+     * API to query if a secure session configuration is supported if the device supports this
+     * API.</p>
+     * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
+     * can assume all output sizes listed in the
+     * {@link android.hardware.camera2.params.StreamConfigurationMap }
+     * are supported.</p>
+     * <p><b>Units</b>: Pixels</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE =
+            new Key<android.util.Size>("android.scaler.defaultSecureImageSize", android.util.Size.class);
+
+    /**
      * <p>The area of the image sensor which corresponds to active pixels after any geometric
      * distortion correction has been applied.</p>
      * <p>This is the rectangle representing the size of the active region of the sensor (i.e.
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/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 886a8c1..a9bcdeff 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -574,12 +574,23 @@
                 mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.w(TAG, "Remote exception in remove: ", e);
-                if (callback != null) {
-                    callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
-                            getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
-                                0 /* vendorCode */));
-                }
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Removes all face templates for the given user.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void removeAll(int userId, @NonNull RemovalCallback callback) {
+        if (mService != null) {
+            try {
+                mRemovalCallback = callback;
+                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
     }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a3e7e2d..6e7c701 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -32,7 +33,7 @@
 interface IFaceService {
 
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Requests a proto dump of the specified sensor
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
@@ -83,10 +84,13 @@
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
 
-    // Any errors resulting from this call will be returned to the listener
+    // Removes the specified face enrollment for the specified userId.
     void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
             String opPackageName);
 
+    // Removes all face enrollments for the specified userId.
+    void removeAll(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+
     // Get the enrolled face for user.
     List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName);
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index a614ebf..9d086cf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -154,7 +154,8 @@
     public BiometricTestSession createTestSession(int sensorId) {
         try {
             return new BiometricTestSession(mContext, sensorId,
-                    mService.createTestSession(sensorId, mContext.getOpPackageName()));
+                    (context, sensorId1, callback) -> mService
+                            .createTestSession(sensorId1, callback, context.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -739,11 +740,22 @@
             mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception in remove: ", e);
-            if (callback != null) {
-                callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                        getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes all face templates for the given user.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void removeAll(int userId, @NonNull RemovalCallback callback) {
+        if (mService != null) {
+            try {
+                mRemovalCallback = callback;
+                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
     }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 8888247..054c0d0 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -33,7 +34,7 @@
 interface IFingerprintService {
 
     // Creates a test session with the specified sensorId
-    ITestSession createTestSession(int sensorId, String opPackageName);
+    ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Requests a proto dump of the specified sensor
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
@@ -87,6 +88,9 @@
     void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
             String opPackageName);
 
+    // Removes all face enrollments for the specified userId.
+    void removeAll(IBinder token, int userId, IFingerprintServiceReceiver receiver, String opPackageName);
+
     // Rename the fingerprint specified by fingerId and userId to the given name
     void rename(int fingerId, int userId, String name);
 
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/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ebb3021..a65f36b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -849,9 +850,18 @@
             attributionTag = context.getAttributionTag();
         }
 
+        // Workaround for old APIs not providing a context
+        String packageName;
+        if (context != null) {
+            packageName = context.getPackageName();
+        } else {
+            packageName = ActivityThread.currentPackageName();
+        }
+
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
+            clientProxy = mService.createClient(
+                    hubInfo.getId(), clientInterface, attributionTag, packageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
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/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 4961195..92882c4 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -60,7 +60,8 @@
 
     // Creates a client to send and receive messages
     IContextHubClient createClient(
-            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
+            int contextHubId, in IContextHubClientCallback client, in String attributionTag,
+            in String packageName);
 
     // Creates a PendingIntent-based client to send and receive messages
     IContextHubClient createPendingIntentClient(
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/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/packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl b/core/java/android/net/ConnectivityMetricsEvent.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl
rename to core/java/android/net/ConnectivityMetricsEvent.aidl
diff --git a/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl b/core/java/android/net/InterfaceConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl
rename to core/java/android/net/InterfaceConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/UidRange.aidl
rename to core/java/android/net/UidRange.aidl
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index 3bc0f9c..b172ccc 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 
 import java.util.Collection;
 
@@ -45,6 +46,14 @@
         return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
     }
 
+    /** Creates a UidRange for the specified user. */
+    public static UidRange createForUser(UserHandle user) {
+        final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1);
+        final int start = UserHandle.getUid(user, 0 /* appId */);
+        final int end = UserHandle.getUid(nextUser, 0) - 1;
+        return new UidRange(start, end);
+    }
+
     /** Returns the smallest user Id which is contained in this UidRange */
     public int getStartUser() {
         return start / PER_USER_RANGE;
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/VpnManager.java
rename to core/java/android/net/VpnManager.java
diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/VpnService.java
rename to core/java/android/net/VpnService.java
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 4f293ee..6a3cb42 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -33,4 +34,7 @@
     void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);
+
+    void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName);
+    void unregisterVcnStatusCallback(in IVcnStatusCallback callback);
 }
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
new file mode 100644
index 0000000..555e9b5
--- /dev/null
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net.vcn;
+
+/** @hide */
+interface IVcnStatusCallback {
+    void onEnteredSafeMode();
+    void onGatewayConnectionError(
+            in int[] gatewayNetworkCapabilities,
+            int errorCode,
+            in String exceptionClass,
+            in String exceptionMessage);
+}
\ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 5eb4ba6..52cc218 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -41,7 +42,6 @@
  * brought up on demand based on active {@link NetworkRequest}(s).
  *
  * @see VcnManager for more information on the Virtual Carrier Network feature
- * @hide
  */
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
@@ -56,7 +56,8 @@
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
         mPackageName = packageName;
-        mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
+        mGatewayConnectionConfigs =
+                Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
 
         validate();
     }
@@ -96,11 +97,7 @@
         return mPackageName;
     }
 
-    /**
-     * Retrieves the set of configured tunnels.
-     *
-     * @hide
-     */
+    /** Retrieves the set of configured GatewayConnection(s). */
     @NonNull
     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -168,11 +165,7 @@
                 }
             };
 
-    /**
-     * This class is used to incrementally build {@link VcnConfig} objects.
-     *
-     * @hide
-     */
+    /** This class is used to incrementally build {@link VcnConfig} objects. */
     public static final class Builder {
         @NonNull private final String mPackageName;
 
@@ -190,7 +183,6 @@
          *
          * @param gatewayConnectionConfig the configuration for an individual gateway connection
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder addGatewayConnectionConfig(
@@ -205,7 +197,6 @@
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
-         * @hide
          */
         @NonNull
         public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnControlPlaneConfig.java b/core/java/android/net/vcn/VcnControlPlaneConfig.java
new file mode 100644
index 0000000..0c6ccfe
--- /dev/null
+++ b/core/java/android/net/vcn/VcnControlPlaneConfig.java
@@ -0,0 +1,114 @@
+/*
+ * 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.net.vcn;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * This class represents a control plane configuration for a Virtual Carrier Network connection.
+ *
+ * <p>Each {@link VcnGatewayConnectionConfig} must have a {@link VcnControlPlaneConfig}, containing
+ * all connection, authentication and authorization parameters required to establish a Gateway
+ * Connection with a remote endpoint.
+ *
+ * <p>A {@link VcnControlPlaneConfig} object can be shared by multiple {@link
+ * VcnGatewayConnectionConfig}(s) if they will used for connecting with the same remote endpoint.
+ *
+ * @see VcnManager
+ * @see VcnGatewayConnectionConfig
+ *
+ * @hide
+ */
+public abstract class VcnControlPlaneConfig {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CONFIG_TYPE_IKE})
+    public @interface ConfigType {}
+
+    /** @hide */
+    public static final int CONFIG_TYPE_IKE = 1;
+
+    private static final String CONFIG_TYPE_KEY = "mConfigType";
+    @ConfigType private final int mConfigType;
+
+    /**
+     * Package private constructor.
+     *
+     * @hide
+     */
+    VcnControlPlaneConfig(int configType) {
+        mConfigType = configType;
+    }
+
+    /**
+     * Constructs a VcnControlPlaneConfig object by deserializing a PersistableBundle.
+     *
+     * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneConfig} object
+     * @hide
+     */
+    public static VcnControlPlaneConfig fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        int configType = in.getInt(CONFIG_TYPE_KEY);
+        switch (configType) {
+            case CONFIG_TYPE_IKE:
+                return new VcnControlPlaneIkeConfig(in);
+            default:
+                throw new IllegalStateException("Unrecognized configType: " + configType);
+        }
+    }
+
+    /**
+     * Converts this VcnControlPlaneConfig to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+        result.putInt(CONFIG_TYPE_KEY, mConfigType);
+        return result;
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConfigType);
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VcnControlPlaneConfig)) {
+            return false;
+        }
+
+        return mConfigType == ((VcnControlPlaneConfig) o).mConfigType;
+    }
+
+    /**
+     * Returns a deep copy of this object.
+     *
+     * @hide
+     */
+    public abstract VcnControlPlaneConfig copy();
+}
diff --git a/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java
new file mode 100644
index 0000000..2f6e1f6
--- /dev/null
+++ b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.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 android.net.vcn;
+
+import static android.net.vcn.VcnControlPlaneConfig.CONFIG_TYPE_IKE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+
+import java.util.Objects;
+
+/**
+ * This class is an IKEv2 control plane configuration for a Virtual Carrier Network connection.
+ *
+ * <p>This class is an extension of the {@link VcnControlPlaneConfig}, containing IKEv2-specific
+ * configuration, authentication and authorization parameters.
+ *
+ * @see VcnControlPlaneConfig
+ *
+ * @hide
+ */
+public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig {
+    private static final String TAG = VcnControlPlaneIkeConfig.class.getSimpleName();
+
+    // STOPSHIP: b/163604823 Make mIkeParams and mChildParams @NonNull when it is supported to
+    // construct mIkeParams and mChildParams from PersistableBundles.
+
+    private static final String IKE_PARAMS_KEY = "mIkeParams";
+    @Nullable private final IkeSessionParams mIkeParams;
+
+    private static final String CHILD_PARAMS_KEY = "mChildParams";
+    @Nullable private final TunnelModeChildSessionParams mChildParams;
+
+    private static final ArraySet<String> BUNDLE_KEY_SET = new ArraySet<>();
+
+    {
+        BUNDLE_KEY_SET.add(IKE_PARAMS_KEY);
+        BUNDLE_KEY_SET.add(CHILD_PARAMS_KEY);
+    }
+
+    /**
+     * Constructs a VcnControlPlaneIkeConfig object.
+     *
+     * @param ikeParams the IKE Session negotiation parameters
+     * @param childParams the tunnel mode Child Session negotiation parameters
+     */
+    public VcnControlPlaneIkeConfig(
+            @NonNull IkeSessionParams ikeParams,
+            @NonNull TunnelModeChildSessionParams childParams) {
+        super(CONFIG_TYPE_IKE);
+        mIkeParams = ikeParams;
+        mChildParams = childParams;
+        validate();
+    }
+
+    /**
+     * Constructs a VcnControlPlaneIkeConfig object by deserializing a PersistableBundle.
+     *
+     * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneIkeConfig} object
+     * @hide
+     */
+    public VcnControlPlaneIkeConfig(@NonNull PersistableBundle in) {
+        super(CONFIG_TYPE_IKE);
+        final PersistableBundle ikeParamsBundle = in.getPersistableBundle(IKE_PARAMS_KEY);
+        final PersistableBundle childParamsBundle = in.getPersistableBundle(CHILD_PARAMS_KEY);
+
+        // STOPSHIP: b/163604823 Support constructing mIkeParams and mChildParams from
+        // PersistableBundles.
+
+        mIkeParams = null;
+        mChildParams = null;
+    }
+
+    private void validate() {
+        Objects.requireNonNull(mIkeParams, "mIkeParams was null");
+        Objects.requireNonNull(mChildParams, "mChildParams was null");
+    }
+
+    /**
+     * Converts this VcnControlPlaneConfig to a PersistableBundle.
+     *
+     * @hide
+     */
+    @Override
+    @NonNull
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+
+        // STOPSHIP: b/163604823 Support converting mIkeParams and mChildParams to
+        // PersistableBundles.
+        return result;
+    }
+
+    /** Retrieves the IKE Session configuration. */
+    @NonNull
+    public IkeSessionParams getIkeSessionParams() {
+        return mIkeParams;
+    }
+
+    /** Retrieves the tunnel mode Child Session configuration. */
+    @NonNull
+    public TunnelModeChildSessionParams getChildSessionParams() {
+        return mChildParams;
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mIkeParams, mChildParams);
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VcnControlPlaneIkeConfig)) {
+            return false;
+        }
+
+        VcnControlPlaneIkeConfig other = (VcnControlPlaneIkeConfig) o;
+
+        // STOPSHIP: b/163604823 Also check mIkeParams and mChildParams when it is supported to
+        // construct mIkeParams and mChildParams from PersistableBundles. They are not checked
+        // now so that VcnGatewayConnectionConfigTest and VcnConfigTest can pass.
+        return super.equals(o);
+    }
+
+    /** @hide */
+    @Override
+    public VcnControlPlaneConfig copy() {
+        return new VcnControlPlaneIkeConfig(
+                new IkeSessionParams.Builder(mIkeParams).build(),
+                new TunnelModeChildSessionParams.Builder(mChildParams).build());
+    }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index cead2f1..94dff91 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -21,6 +21,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
@@ -55,28 +57,23 @@
  * subscription group under which this configuration is registered (see {@link
  * VcnManager#setVcnConfig}).
  *
- * <p>Services that can be provided by a VCN network, or required for underlying networks are
- * limited to services provided by cellular networks:
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or
+ * required for underlying networks are limited to services provided by cellular networks:
  *
  * <ul>
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
- *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
  * </ul>
- *
- * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the
- * underlying Network(s).
- *
- * @hide
  */
 public final class VcnGatewayConnectionConfig {
     // TODO: Use MIN_MTU_V6 once it is public, @hide
@@ -153,14 +150,15 @@
                 TimeUnit.MINUTES.toMillis(15)
             };
 
+    private static final String CTRL_PLANE_CONFIG_KEY = "mCtrlPlaneConfig";
+    @NonNull private VcnControlPlaneConfig mCtrlPlaneConfig;
+
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
     @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
     private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities";
     @NonNull private final SortedSet<Integer> mUnderlyingCapabilities;
 
-    // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig
-
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
 
@@ -169,10 +167,12 @@
 
     /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
     private VcnGatewayConnectionConfig(
+            @NonNull VcnControlPlaneConfig ctrlPlaneConfig,
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull Set<Integer> underlyingCapabilities,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
+        mCtrlPlaneConfig = ctrlPlaneConfig;
         mExposedCapabilities = new TreeSet(exposedCapabilities);
         mUnderlyingCapabilities = new TreeSet(underlyingCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
@@ -184,11 +184,16 @@
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
+        final PersistableBundle ctrlPlaneConfigBundle =
+                in.getPersistableBundle(CTRL_PLANE_CONFIG_KEY);
+        Objects.requireNonNull(ctrlPlaneConfigBundle, "ctrlPlaneConfigBundle was null");
+
         final PersistableBundle exposedCapsBundle =
                 in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
         final PersistableBundle underlyingCapsBundle =
                 in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY);
 
+        mCtrlPlaneConfig = VcnControlPlaneConfig.fromPersistableBundle(ctrlPlaneConfigBundle);
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
         mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
@@ -200,6 +205,8 @@
     }
 
     private void validate() {
+        Objects.requireNonNull(mCtrlPlaneConfig, "control plane config was null");
+
         Preconditions.checkArgument(
                 mExposedCapabilities != null && !mExposedCapabilities.isEmpty(),
                 "exposedCapsBundle was null or empty");
@@ -243,14 +250,23 @@
     }
 
     /**
+     * Returns control plane configuration.
+     *
+     * @hide
+     */
+    @NonNull
+    public VcnControlPlaneConfig getControlPlaneConfig() {
+        return mCtrlPlaneConfig.copy();
+    }
+
+    /**
      * Returns all exposed capabilities.
      *
      * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
      * ascending numerical order.
      *
      * @see Builder#addExposedCapability(int)
-     * @see Builder#clearExposedCapability(int)
-     * @hide
+     * @see Builder#removeExposedCapability(int)
      */
     @NonNull
     public int[] getExposedCapabilities() {
@@ -278,8 +294,7 @@
      * <p>The returned integer-value capabilities will be sorted in ascending numerical order.
      *
      * @see Builder#addRequiredUnderlyingCapability(int)
-     * @see Builder#clearRequiredUnderlyingCapability(int)
-     * @hide
+     * @see Builder#removeRequiredUnderlyingCapability(int)
      */
     @NonNull
     public int[] getRequiredUnderlyingCapabilities() {
@@ -305,7 +320,6 @@
      * Retrieves the configured retry intervals.
      *
      * @see Builder#setRetryInterval(long[])
-     * @hide
      */
     @NonNull
     public long[] getRetryInterval() {
@@ -317,7 +331,7 @@
      *
      * <p>Left to prevent the need to make major changes while changes are actively in flight.
      *
-     * @deprecated use getRequiredUnderlyingCapabilities() instead
+     * @deprecated use getRetryInterval() instead
      * @hide
      */
     @Deprecated
@@ -329,8 +343,7 @@
     /**
      * Retrieves the maximum MTU allowed for this Gateway Connection.
      *
-     * @see Builder.setMaxMtu(int)
-     * @hide
+     * @see Builder#setMaxMtu(int)
      */
     @IntRange(from = MIN_MTU_V6)
     public int getMaxMtu() {
@@ -347,6 +360,7 @@
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = new PersistableBundle();
 
+        final PersistableBundle ctrlPlaneConfigBundle = mCtrlPlaneConfig.toPersistableBundle();
         final PersistableBundle exposedCapsBundle =
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
@@ -356,6 +370,7 @@
                         new ArrayList<>(mUnderlyingCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
 
+        result.putPersistableBundle(CTRL_PLANE_CONFIG_KEY, ctrlPlaneConfigBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
         result.putPersistableBundle(UNDERLYING_CAPABILITIES_KEY, underlyingCapsBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
@@ -388,10 +403,9 @@
 
     /**
      * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
-     *
-     * @hide
      */
     public static final class Builder {
+        @NonNull private final VcnControlPlaneConfig mCtrlPlaneConfig;
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
         @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet();
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
@@ -402,6 +416,26 @@
         //       when on Cell.
 
         /**
+         * Construct a Builder object.
+         *
+         * @param ctrlPlaneConfig the control plane configuration
+         * @see VcnControlPlaneConfig
+         * @hide
+         */
+        public Builder(@NonNull VcnControlPlaneConfig ctrlPlaneConfig) {
+            Objects.requireNonNull(ctrlPlaneConfig, "ctrlPlaneConfig was null");
+
+            mCtrlPlaneConfig = ctrlPlaneConfig;
+        }
+
+        /** Construct a Builder object. */
+        // TODO: Remove this constructor when #Builder(ctrlPlaneConfig) is exposed as public API.
+        // This constructor is created to avoid changing API shape in this CL
+        public Builder() {
+            mCtrlPlaneConfig = null;
+        }
+
+        /**
          * Add a capability that this VCN Gateway Connection will support.
          *
          * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway
@@ -409,7 +443,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
         public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
@@ -427,10 +460,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
-         * @hide
          */
         @NonNull
-        public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.remove(exposedCapability);
@@ -445,7 +478,6 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
         public Builder addRequiredUnderlyingCapability(
@@ -468,10 +500,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
-         * @hide
          */
         @NonNull
-        public Builder clearRequiredUnderlyingCapability(
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeRequiredUnderlyingCapability(
                 @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
@@ -501,7 +533,6 @@
          *     15m]}
          * @return this {@link Builder} instance, for chaining
          * @see VcnManager for additional discussion on fail-safe mode
-         * @hide
          */
         @NonNull
         public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -523,7 +554,6 @@
          * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
          *     the IPv6 minimum MTU of 1280. Defaults to 1500.
          * @return this {@link Builder} instance, for chaining
-         * @hide
          */
         @NonNull
         public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -538,12 +568,15 @@
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
-         * @hide
          */
         @NonNull
         public VcnGatewayConnectionConfig build() {
             return new VcnGatewayConnectionConfig(
-                    mExposedCapabilities, mUnderlyingCapabilities, mRetryIntervalsMs, mMaxMtu);
+                    mCtrlPlaneConfig,
+                    mExposedCapabilities,
+                    mUnderlyingCapabilities,
+                    mRetryIntervalsMs,
+                    mMaxMtu);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1a38338..aea0ea9 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -17,12 +17,15 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.os.Binder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -31,6 +34,8 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -39,12 +44,12 @@
 /**
  * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
  *
- * <p>A VCN creates a virtualization layer to allow MVNOs to aggregate heterogeneous physical
+ * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
  * networks, unifying them as a single carrier network. This enables infrastructure flexibility on
- * the part of MVNOs without impacting user connectivity, abstracting the physical network
+ * the part of carriers without impacting user connectivity, abstracting the physical network
  * technologies as an implementation detail of their public network.
  *
- * <p>Each VCN virtualizes an Carrier's network by building tunnels to a carrier's core network over
+ * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
  * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
  * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
  * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
@@ -62,8 +67,6 @@
  * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
  * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
  * automatically exit Safe Mode if all active tunnels connect successfully.
- *
- * @hide
  */
 @SystemService(Context.VCN_MANAGEMENT_SERVICE)
 public class VcnManager {
@@ -101,7 +104,6 @@
         return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Sets the VCN configuration for a given subscription group.
      *
@@ -113,11 +115,10 @@
      *
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @param config the configuration parameters for the VCN
-     * @throws SecurityException if the caller does not have carrier privileges, or is not running
-     *     as the primary user
-     * @throws IOException if the configuration failed to be persisted. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws SecurityException if the caller does not have carrier privileges for the provided
+     *     subscriptionGroup, or is not running as the primary user
+     * @throws IOException if the configuration failed to be saved and persisted to disk. This may
+     *     occur due to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
@@ -134,7 +135,6 @@
         }
     }
 
-    // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Clears the VCN configuration for a given subscription group.
      *
@@ -145,9 +145,8 @@
      * @param subscriptionGroup the subscription group that the configuration should be applied to
      * @throws SecurityException if the caller does not have carrier privileges, or is not running
      *     as the primary user
-     * @throws IOException if the configuration failed to be cleared. A caller encountering this
-     *     exception should attempt to retry (possibly after a delay).
-     * @hide
+     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
+     *     to temporary disk errors, or more permanent conditions such as a full disk.
      */
     @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
@@ -267,6 +266,154 @@
         }
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        VCN_ERROR_CODE_INTERNAL_ERROR,
+        VCN_ERROR_CODE_CONFIG_ERROR,
+        VCN_ERROR_CODE_NETWORK_ERROR
+    })
+    public @interface VcnErrorCode {}
+
+    /**
+     * Value indicating that an internal failure occurred in this Gateway Connection.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
+
+    /**
+     * Value indicating that an error with this Gateway Connection's configuration occurred.
+     *
+     * <p>For example, this error code will be returned after authentication failures.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
+
+    /**
+     * Value indicating that a Network error occurred with this Gateway Connection.
+     *
+     * <p>For example, this error code will be returned if an underlying {@link android.net.Network}
+     * for this Gateway Connection is lost, or if an error occurs while resolving the connection
+     * endpoint address.
+     *
+     * @hide
+     */
+    public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
+
+    // TODO: make VcnStatusCallback @SystemApi
+    /**
+     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
+     *
+     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
+     * subscription group.
+     *
+     * @hide
+     */
+    public abstract static class VcnStatusCallback {
+        private VcnStatusCallbackBinder mCbBinder;
+
+        /**
+         * Invoked when the VCN for this Callback's subscription group enters safe mode.
+         *
+         * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
+         * establish a connection within a system-determined timeout (while underlying networks were
+         * available).
+         *
+         * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
+         * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
+         */
+        public abstract void onEnteredSafeMode();
+
+        /**
+         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription
+         * encounters an error.
+         *
+         * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway
+         *     Connection that encountered the error for identification purposes. These will be a
+         *     sorted list with no duplicates, matching one of the {@link
+         *     VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription
+         *     group.
+         * @param errorCode {@link VcnErrorCode} to indicate the error that occurred
+         * @param detail Throwable to provide additional information about the error, or {@code
+         *     null} if none
+         */
+        public abstract void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable Throwable detail);
+    }
+
+    /**
+     * Registers the given callback to receive status updates for the specified subscription.
+     *
+     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
+     *
+     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
+     * VcnStatusCallback}s may be reused once unregistered.
+     *
+     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
+     * privileges for the specified subscription at the time of invocation.
+     *
+     * @param subscriptionGroup The subscription group to match for callbacks
+     * @param executor The {@link Executor} to be used for invoking callbacks
+     * @param callback The VcnStatusCallback to be registered
+     * @throws IllegalStateException if callback is currently registered with VcnManager
+     * @hide
+     */
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Executor executor,
+            @NonNull VcnStatusCallback callback) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder != null) {
+                throw new IllegalStateException("callback is already registered with VcnManager");
+            }
+            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
+
+            try {
+                mService.registerVcnStatusCallback(
+                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                callback.mCbBinder = null;
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the given callback.
+     *
+     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
+     * was registered with.
+     *
+     * @param callback The callback to be unregistered
+     * @hide
+     */
+    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder == null) {
+                // no Binder attached to this callback, so it's not currently registered
+                return;
+            }
+
+            try {
+                mService.unregisterVcnStatusCallback(callback.mCbBinder);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } finally {
+                callback.mCbBinder = null;
+            }
+        }
+    }
+
     /**
      * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
      * Server.
@@ -286,7 +433,62 @@
 
         @Override
         public void onPolicyChanged() {
-            mExecutor.execute(() -> mListener.onPolicyChanged());
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
+        }
+    }
+
+    /**
+     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
+        @NonNull private final Executor mExecutor;
+        @NonNull private final VcnStatusCallback mCallback;
+
+        public VcnStatusCallbackBinder(
+                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onEnteredSafeMode() {
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
+        }
+
+        // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
+
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            mExecutor.execute(
+                                    () ->
+                                            mCallback.onGatewayConnectionError(
+                                                    networkCapabilities, errorCode, cause)));
+        }
+
+        private static Throwable createThrowableByClassName(
+                @Nullable String className, @Nullable String message) {
+            if (className == null) {
+                return null;
+            }
+
+            try {
+                Class<?> c = Class.forName(className);
+                return (Throwable) c.getConstructor(String.class).newInstance(message);
+            } catch (ReflectiveOperationException | ClassCastException e) {
+                return new RuntimeException(className + ": " + message);
+            }
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
new file mode 100644
index 0000000..a975637
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
@@ -0,0 +1,125 @@
+/*
+ * 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.net.vcn;
+
+import android.annotation.NonNull;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * NetworkSpecifier object for VCN underlying network requests.
+ *
+ * <p>This matches any underlying network with the appropriate subIds.
+ *
+ * @hide
+ */
+public final class VcnUnderlyingNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    @NonNull private final int[] mSubIds;
+
+    /**
+     * Builds a new VcnUnderlyingNetworkSpecifier with the given list of subIds
+     *
+     * @hide
+     */
+    public VcnUnderlyingNetworkSpecifier(@NonNull int[] subIds) {
+        mSubIds = Objects.requireNonNull(subIds, "subIds were null");
+    }
+
+    /**
+     * Retrieves the list of subIds supported by this VcnUnderlyingNetworkSpecifier
+     *
+     * @hide
+     */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public int[] getSubIds() {
+        return mSubIds;
+    }
+
+    public static final @NonNull Creator<VcnUnderlyingNetworkSpecifier> CREATOR =
+            new Creator<VcnUnderlyingNetworkSpecifier>() {
+                @Override
+                public VcnUnderlyingNetworkSpecifier createFromParcel(Parcel in) {
+                    int[] subIds = in.createIntArray();
+                    return new VcnUnderlyingNetworkSpecifier(subIds);
+                }
+
+                @Override
+                public VcnUnderlyingNetworkSpecifier[] newArray(int size) {
+                    return new VcnUnderlyingNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeIntArray(mSubIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mSubIds);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof VcnUnderlyingNetworkSpecifier)) {
+            return false;
+        }
+
+        VcnUnderlyingNetworkSpecifier lhs = (VcnUnderlyingNetworkSpecifier) obj;
+        return Arrays.equals(mSubIds, lhs.mSubIds);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("VcnUnderlyingNetworkSpecifier [")
+                .append("mSubIds = ").append(Arrays.toString(mSubIds))
+                .append("]")
+                .toString();
+    }
+
+    /** @hide */
+    @Override
+    public boolean canBeSatisfiedBy(NetworkSpecifier other) {
+        if (other instanceof TelephonyNetworkSpecifier) {
+            return ArrayUtils.contains(
+                    mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId());
+        }
+        // TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier
+
+        // MatchAllNetworkSpecifier matched in NetworkCapabilities.
+        return equals(other);
+    }
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index eac03dc..e3b13f4 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2566,6 +2566,14 @@
     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.
      *
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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b39c182..7437e037 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -113,6 +113,7 @@
     boolean hasBadge(int userId);
     boolean isUserUnlocked(int userId);
     boolean isUserRunning(int userId);
+    boolean isUserForeground();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles();
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index dac1ede..a04047d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,4 +1,6 @@
 # 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
@@ -6,9 +8,11 @@
 per-file IVibratorManagerService.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/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/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/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index a828077..bb40d90 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -29,21 +29,11 @@
     private final int mUid;
     @Nullable
     private final String mPackageWithHighestDrain;
-    private boolean mSystemComponent;
 
     public int getUid() {
         return mUid;
     }
 
-    /**
-     * Returns true if this battery consumer is considered to be a part of the operating
-     * system itself. For example, the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID}
-     * is a system component.
-     */
-    public boolean isSystemComponent() {
-        return mSystemComponent;
-    }
-
     @Nullable
     public String getPackageWithHighestDrain() {
         return mPackageWithHighestDrain;
@@ -52,7 +42,6 @@
     private UidBatteryConsumer(@NonNull Builder builder) {
         super(builder.mPowerComponentsBuilder.build());
         mUid = builder.mUid;
-        mSystemComponent = builder.mSystemComponent;
         mPackageWithHighestDrain = builder.mPackageWithHighestDrain;
     }
 
@@ -95,7 +84,6 @@
         private final BatteryStats.Uid mBatteryStatsUid;
         private final int mUid;
         private String mPackageWithHighestDrain;
-        private boolean mSystemComponent;
         private boolean mExcludeFromBatteryUsageStats;
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount,
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ea1ce37..8bdfd3d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2300,6 +2300,19 @@
     }
 
     /**
+     * Checks if the calling user is running on foreground.
+     *
+     * @return whether the calling user is running on foreground.
+     */
+    public boolean isUserForeground() {
+        try {
+            return mService.isUserForeground();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the calling user is running in an "unlocked" state.
      * <p>
      * On devices with direct boot, a user is unlocked only after they've
diff --git a/core/java/android/permission/AdminPermissionControlParams.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl
new file mode 100644
index 0000000..35e63d4
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.permission;
+
+parcelable AdminPermissionControlParams;
diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java
new file mode 100644
index 0000000..49507220
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.java
@@ -0,0 +1,132 @@
+/*
+ * 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.permission;
+
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A data object representing an admin's request to control a certain permission
+ * for a certain app.
+ * This class is processed by the Permission Controller's
+ * setRuntimePermissionGrantStateByDeviceAdmin method.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AdminPermissionControlParams implements Parcelable {
+    // The package to grant/deny the permission to.
+    private final @NonNull String mGranteePackageName;
+    // The permission to grant/deny.
+    private final @NonNull String mPermission;
+    // The grant state (granted/denied/default).
+    private final @DevicePolicyManager.PermissionGrantState int mGrantState;
+    // Whether the admin can grant sensors-related permissions.
+    private final boolean mCanAdminGrantSensorsPermissions;
+
+    /**
+     * @hide
+     * A new instance is only created by the framework, so the constructor need not be visible
+     * as system API.
+     */
+    public AdminPermissionControlParams(@NonNull String granteePackageName,
+            @NonNull String permission,
+            int grantState, boolean canAdminGrantSensorsPermissions) {
+        Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty.");
+        Preconditions.checkStringNotEmpty(permission, "Permission must not be empty.");
+        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
+                || grantState == PERMISSION_GRANT_STATE_DENIED
+                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
+
+        mGranteePackageName = granteePackageName;
+        mPermission = permission;
+        mGrantState = grantState;
+        mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions;
+    }
+
+    public static final @NonNull Creator<AdminPermissionControlParams> CREATOR =
+            new Creator<AdminPermissionControlParams>() {
+                @Override
+                public AdminPermissionControlParams createFromParcel(Parcel in) {
+                    String granteePackageName = in.readString();
+                    String permission = in.readString();
+                    int grantState = in.readInt();
+                    boolean mayAdminGrantSensorPermissions = in.readBoolean();
+
+                    return new AdminPermissionControlParams(granteePackageName, permission,
+                            grantState, mayAdminGrantSensorPermissions);
+                }
+
+                @Override
+                public AdminPermissionControlParams[] newArray(int size) {
+                    return new AdminPermissionControlParams[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mGranteePackageName);
+        dest.writeString(mPermission);
+        dest.writeInt(mGrantState);
+        dest.writeBoolean(mCanAdminGrantSensorsPermissions);
+    }
+
+    /** Returns the name of the package the permission applies to */
+    public @NonNull String getGranteePackageName() {
+        return mGranteePackageName;
+    }
+
+    /** Returns the permission name */
+    public @NonNull String getPermission() {
+        return mPermission;
+    }
+
+    /** Returns the grant state */
+    public int getGrantState() {
+        return mGrantState;
+    }
+
+    /**
+     * return true if the admin may control grants of permissions related to sensors.
+     */
+    public boolean canAdminGrantSensorsPermissions() {
+        return mCanAdminGrantSensorsPermissions;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b",
+                mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions);
+    }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 084cc2f..6d677f3 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.permission.AdminPermissionControlParams;
 import com.android.internal.infra.AndroidFuture;
 
 /**
@@ -39,8 +40,8 @@
     void countPermissionApps(in List<String> permissionNames, int flags,
             in AndroidFuture callback);
     void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback);
-    void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
-                String permission, int grantState, in AndroidFuture callback);
+    void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName,
+            in AdminPermissionControlParams params, in AndroidFuture callback);
     void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
     void notifyOneTimePermissionSessionTimeout(String packageName);
     void updateUserSensitiveForApp(int uid, in AndroidFuture callback);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index f306805..084b18e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -16,13 +16,9 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
 
 import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
-import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkFlagsArgument;
@@ -39,7 +35,6 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager.PermissionGrantState;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -70,6 +65,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -323,11 +319,11 @@
 
     /**
      * Set the runtime permission state from a device admin.
+     * This variant takes into account whether the admin may or may not grant sensors-related
+     * permissions.
      *
      * @param callerPackageName The package name of the admin requesting the change
-     * @param packageName Package the permission belongs to
-     * @param permission Permission to change
-     * @param grantState State to set the permission into
+     * @param params Information about the permission being granted.
      * @param executor Executor to run the {@code callback} on
      * @param callback The callback
      *
@@ -338,30 +334,27 @@
             Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY},
             conditional = true)
     public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
-            @NonNull String packageName, @NonNull String permission,
-            @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor,
+            @NonNull AdminPermissionControlParams params,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         checkStringNotEmpty(callerPackageName);
-        checkStringNotEmpty(packageName);
-        checkStringNotEmpty(permission);
-        checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                || grantState == PERMISSION_GRANT_STATE_DENIED
-                || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-        checkNotNull(executor);
-        checkNotNull(callback);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Objects.requireNonNull(params, "Admin control params must not be null.");
 
         mRemoteService.postAsync(service -> {
             AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
-            service.setRuntimePermissionGrantStateByDeviceAdmin(
-                    callerPackageName, packageName, permission, grantState,
+            service.setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    callerPackageName, params,
                     setRuntimePermissionGrantStateResult);
             return setRuntimePermissionGrantStateResult;
         }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
             final long token = Binder.clearCallingIdentity();
             try {
                 if (err != null) {
-                    Log.e(TAG, "Error setting permissions state for device admin " + packageName,
-                            err);
+                    Log.e(TAG,
+                            "Error setting permissions state for device admin "
+                                    + callerPackageName, err);
                     callback.accept(false);
                 } else {
                     callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult));
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8105b65..ad9e8b3 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -16,9 +16,7 @@
 
 package android.permission;
 
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
 
@@ -259,6 +257,8 @@
     }
 
     /**
+     * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String,
+     * AdminPermissionControlParams, Consumer)}.
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -267,6 +267,7 @@
      * @param grantState State to set the permission into
      * @param callback Callback waiting for whether the state could be set or not
      */
+    @Deprecated
     @BinderThread
     public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(
             @NonNull String callerPackageName, @NonNull String packageName,
@@ -274,6 +275,20 @@
             @NonNull Consumer<Boolean> callback);
 
     /**
+     * Set the runtime permission state from a device admin.
+     *
+     * @param callerPackageName The package name of the admin requesting the change
+     * @param params Parameters of admin request.
+     * @param callback Callback waiting for whether the state could be set or not
+     */
+    @BinderThread
+    public void onSetRuntimePermissionGrantStateByDeviceAdmin(
+            @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params,
+            @NonNull Consumer<Boolean> callback) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
+    /**
      * Called when a package is considered inactive based on the criteria given by
      * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
      * This method is called at the end of a one-time permission session
@@ -468,32 +483,26 @@
             }
 
             @Override
-            public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
-                    String packageName, String permission, int grantState,
+            public void setRuntimePermissionGrantStateByDeviceAdminFromParams(
+                    String callerPackageName, AdminPermissionControlParams params,
                     AndroidFuture callback) {
                 checkStringNotEmpty(callerPackageName);
-                checkStringNotEmpty(packageName);
-                checkStringNotEmpty(permission);
-                checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
-                        || grantState == PERMISSION_GRANT_STATE_DENIED
-                        || grantState == PERMISSION_GRANT_STATE_DEFAULT);
-                checkNotNull(callback);
-
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
                 }
 
-                if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
                 }
 
                 enforceSomePermissionsGrantedToCaller(
                         Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+                checkNotNull(callback);
 
                 onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
-                        packageName, permission, grantState, callback::complete);
+                        params, callback::complete);
             }
 
             @Override
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 91e091c..e134c29 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -525,6 +525,13 @@
      */
     public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
 
+    /**
+     * Namespace for game overlay related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef81ed7..e979e13 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1902,6 +1902,18 @@
     public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
 
     /**
+     * Activity Extra: An {@code Arraylist<String>} of {@link NotificationChannel} field names to
+     * show on the Settings UI.
+     *
+     * <p>
+     * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If
+     * included the system will filter out any Settings that doesn't appear in this list that
+     * otherwise would display.
+     */
+    public static final String EXTRA_CHANNEL_FILTER_LIST
+            = "android.provider.extra.CHANNEL_FILTER_LIST";
+
+    /**
      * Activity Action: Show notification redaction settings.
      *
      * @hide
@@ -10780,6 +10792,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/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index 2efc212..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -29,11 +29,8 @@
 import android.annotation.WorkerThread;
 import android.content.ContentResolver;
 import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 
@@ -63,7 +60,6 @@
      *
      * @hide
      */
-    @SystemApi
     public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
 
     private SimPhonebookContract() {
@@ -76,7 +72,6 @@
      * @hide
      */
     @NonNull
-    @SystemApi
     public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
         switch (efType) {
             case EF_ADN:
@@ -122,12 +117,12 @@
          * The name for this record.
          *
          * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
-         * exceeds the maximum supported length or contains unsupported characters.
-         * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
-         * check whether the name is supported.
+         * exceeds the maximum supported length. Use
+         * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name
+         * will be after encoding.
          *
          * @see ElementaryFiles#NAME_MAX_LENGTH
-         * @see #validateName(ContentResolver, int, int, String) )
+         * @see #getEncodedNameLength(ContentResolver, String)
          */
         public static final String NAME = "name";
         /**
@@ -149,24 +144,31 @@
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
 
         /**
-         * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
-         * that the following path segment contains a name to be validated.
-         *
-         * @hide
-         * @see #validateName(ContentResolver, int, int, String)
+         * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name
+         * length could not be determined because the name could not be encoded.
          */
-        @SystemApi
-        public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+        public static final int ERROR_NAME_UNSUPPORTED = -1;
 
         /**
-         * The key for a cursor extra that contains the result of a validate name query.
+         * The method name used to get the encoded length of a value for {@link SimRecords#NAME}
+         * column.
          *
          * @hide
-         * @see #validateName(ContentResolver, int, int, String)
+         * @see #getEncodedNameLength(ContentResolver, String)
+         * @see ContentResolver#call(String, String, String, Bundle)
          */
-        @SystemApi
-        public static final String EXTRA_NAME_VALIDATION_RESULT =
-                "android.provider.extra.NAME_VALIDATION_RESULT";
+        public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length";
+
+        /**
+         * Extra key used for an integer value that contains the length in bytes of an encoded
+         * name.
+         *
+         * @hide
+         * @see #getEncodedNameLength(ContentResolver, String)
+         * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME
+         */
+        public static final String EXTRA_ENCODED_NAME_LENGTH =
+                "android.provider.extra.ENCODED_NAME_LENGTH";
 
 
         /**
@@ -244,32 +246,34 @@
         }
 
         /**
-         * Validates a value that is being provided for the {@link #NAME} column.
+         * Returns the number of bytes required to encode the specified name when it is stored
+         * on the SIM.
          *
-         * <p>The return value can be used to check if the name is valid. If it is not valid then
-         * inserts and updates to the specified elementary file that use the provided name value
-         * will throw an {@link IllegalArgumentException}.
+         * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name
+         * may require more than 1 byte per character depending on the characters it contains. So
+         * this method can be used to check whether a name exceeds the max length.
          *
-         * <p>If the specified SIM or elementary file don't exist then
-         * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
-         * {@link NameValidationResult#isValid()} will return false.
+         * @return the number of bytes required by the encoded name or
+         * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded.
+         * @throws IllegalStateException if the provider fails to return the length.
+         * @see SimRecords#NAME
+         * @see ElementaryFiles#NAME_MAX_LENGTH
          */
-        @NonNull
         @WorkerThread
-        public static NameValidationResult validateName(
-                @NonNull ContentResolver resolver, int subscriptionId,
-                @ElementaryFiles.EfType int efType,
-                @NonNull String name) {
-            Bundle queryArgs = new Bundle();
-            queryArgs.putString(SimRecords.NAME, name);
-            try (Cursor cursor =
-                         resolver.query(buildContentUri(subscriptionId, efType)
-                                 .appendPath(VALIDATE_NAME_PATH_SEGMENT)
-                                 .build(), null, queryArgs, null)) {
-                NameValidationResult result = cursor.getExtras()
-                        .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
-                return result != null ? result : new NameValidationResult(name, "", 0, 0);
+        public static int getEncodedNameLength(
+                @NonNull ContentResolver resolver, @NonNull String name) {
+            Objects.requireNonNull(name);
+            Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name,
+                    null);
+            if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) {
+                throw new IllegalStateException("Provider malfunction: no length was returned.");
             }
+            int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED);
+            if (length < 0 && length != ERROR_NAME_UNSUPPORTED) {
+                throw new IllegalStateException(
+                        "Provider malfunction: invalid length was returned.");
+            }
+            return length;
         }
 
         private static Uri.Builder buildContentUri(
@@ -281,106 +285,6 @@
                     .appendPath(getEfUriPath(efType));
         }
 
-        /** Contains details about the validity of a value provided for the {@link #NAME} column. */
-        public static final class NameValidationResult implements Parcelable {
-
-            @NonNull
-            public static final Creator<NameValidationResult> CREATOR =
-                    new Creator<NameValidationResult>() {
-
-                        @Override
-                        public NameValidationResult createFromParcel(@NonNull Parcel in) {
-                            return new NameValidationResult(in);
-                        }
-
-                        @NonNull
-                        @Override
-                        public NameValidationResult[] newArray(int size) {
-                            return new NameValidationResult[size];
-                        }
-                    };
-
-            private final String mName;
-            private final String mSanitizedName;
-            private final int mEncodedLength;
-            private final int mMaxEncodedLength;
-
-            /** Creates a new instance from the provided values. */
-            public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
-                    int encodedLength, int maxEncodedLength) {
-                this.mName = Objects.requireNonNull(name);
-                this.mSanitizedName = Objects.requireNonNull(sanitizedName);
-                this.mEncodedLength = encodedLength;
-                this.mMaxEncodedLength = maxEncodedLength;
-            }
-
-            private NameValidationResult(Parcel in) {
-                this(in.readString(), in.readString(), in.readInt(), in.readInt());
-            }
-
-            /** Returns the original name that is being validated. */
-            @NonNull
-            public String getName() {
-                return mName;
-            }
-
-            /**
-             * Returns a sanitized copy of the original name with all unsupported characters
-             * replaced with spaces.
-             */
-            @NonNull
-            public String getSanitizedName() {
-                return mSanitizedName;
-            }
-
-            /**
-             * Returns whether the original name isValid.
-             *
-             * <p>If this returns false then inserts and updates using the name will throw an
-             * {@link IllegalArgumentException}
-             */
-            public boolean isValid() {
-                return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
-                        && Objects.equals(
-                        mName, mSanitizedName);
-            }
-
-            /** Returns whether the character at the specified position is supported by the SIM. */
-            public boolean isSupportedCharacter(int position) {
-                return mName.charAt(position) == mSanitizedName.charAt(position);
-            }
-
-            /**
-             * Returns the number of bytes required to save the name.
-             *
-             * <p>This may be more than the number of characters in the name.
-             */
-            public int getEncodedLength() {
-                return mEncodedLength;
-            }
-
-            /**
-             * Returns the maximum number of bytes that are supported for the name.
-             *
-             * @see ElementaryFiles#NAME_MAX_LENGTH
-             */
-            public int getMaxEncodedLength() {
-                return mMaxEncodedLength;
-            }
-
-            @Override
-            public int describeContents() {
-                return 0;
-            }
-
-            @Override
-            public void writeToParcel(@NonNull Parcel dest, int flags) {
-                dest.writeString(mName);
-                dest.writeString(mSanitizedName);
-                dest.writeInt(mEncodedLength);
-                dest.writeInt(mMaxEncodedLength);
-            }
-        }
     }
 
     /** Constants for metadata about the elementary files of the SIM cards in the phone. */
@@ -446,13 +350,10 @@
          */
         public static final int EF_SDN = 3;
         /** @hide */
-        @SystemApi
         public static final String EF_ADN_PATH_SEGMENT = "adn";
         /** @hide */
-        @SystemApi
         public static final String EF_FDN_PATH_SEGMENT = "fdn";
         /** @hide */
-        @SystemApi
         public static final String EF_SDN_PATH_SEGMENT = "sdn";
         /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
@@ -464,7 +365,6 @@
          *
          * @hide
          */
-        @SystemApi
         public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
 
         /** Content URI for the ADN-like elementary files available on the device. */
@@ -480,8 +380,7 @@
          * Returns a content uri for a specific elementary file.
          *
          * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
-         * If the SIM doesn't support the specified elementary file it will have a zero value for
-         * {@link #MAX_RECORDS}.
+         * If the SIM doesn't support the specified elementary file it will return an empty cursor.
          */
         @NonNull
         public static Uri getItemUri(int subscriptionId, @EfType int efType) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index f013976e..7996f09 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5286,7 +5286,8 @@
          * which network types are allowed for
          * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER},
          * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER},
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}.
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER},
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}.
          * <P>Type: TEXT </P>
          *
          * @hide
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index a79b197..5a89cdf 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -188,6 +188,7 @@
     public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
     public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
     public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;
+    public static final int KM_PURPOSE_ATTEST_KEY = KeyPurpose.ATTEST_KEY;
 
     // Key formats.
     public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 77f9c1a..e7ceada 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1840,13 +1840,15 @@
          *
          * @param allowedNetworkTypesList Map associating all allowed network type reasons
          * ({@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER},
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, and
-         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}) with reason's allowed
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER},
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, and
+         * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}) with reason's allowed
          * network type values.
          * For example:
          * map{{TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER, long type value},
          *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER, long type value},
-         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value}}
+         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value},
+         *     {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, long type value}}
          */
         @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onAllowedNetworkTypesChanged(
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index bfa8bf2..52ec5bd 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -16,8 +16,10 @@
 
 package android.uwb;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.PersistableBundle;
@@ -247,6 +249,7 @@
      *
      * @param params configuration parameters for starting the session
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void start(@NonNull PersistableBundle params) {
         if (mState != State.IDLE) {
             throw new IllegalStateException();
@@ -271,6 +274,7 @@
      *
      * @param params the parameters to reconfigure and their new values
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void reconfigure(@NonNull PersistableBundle params) {
         if (mState != State.ACTIVE && mState != State.IDLE) {
             throw new IllegalStateException();
@@ -302,6 +306,7 @@
      * <p>On failure to stop the session,
      * {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked.
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void stop() {
         if (mState != State.ACTIVE) {
             throw new IllegalStateException();
@@ -333,6 +338,7 @@
      * {@link #close()}, even if the {@link RangingSession} is already closed.
      */
     @Override
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void close() {
         if (mState == State.CLOSED) {
             mExecutor.execute(() -> mCallback.onClosed(
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 8adfe06..2dc0ba0 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -16,9 +16,11 @@
 
 package android.uwb;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -154,6 +156,7 @@
      * @param executor an {@link Executor} to execute given callback
      * @param callback user implementation of the {@link AdapterStateCallback}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull AdapterStateCallback callback) {
         mAdapterStateListener.register(executor, callback);
@@ -168,6 +171,7 @@
      *
      * @param callback user implementation of the {@link AdapterStateCallback}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
         mAdapterStateListener.unregister(callback);
     }
@@ -181,6 +185,7 @@
      * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public PersistableBundle getSpecificationInfo() {
         try {
             return mUwbAdapter.getSpecificationInfo();
@@ -194,6 +199,7 @@
      *
      * @return true if ranging is supported
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public boolean isRangingSupported() {
         try {
             return mUwbAdapter.isRangingSupported();
@@ -250,6 +256,7 @@
      * @return angle of arrival type supported
      */
     @AngleOfArrivalSupportType
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getAngleOfArrivalSupport() {
         try {
             switch (mUwbAdapter.getAngleOfArrivalSupport()) {
@@ -281,6 +288,7 @@
      * @return {@link List} of supported channel numbers ordered by preference
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public List<Integer> getSupportedChannelNumbers() {
         List<Integer> channels = new ArrayList<>();
         try {
@@ -300,6 +308,7 @@
      * @return {@link List} of supported preamble code indices
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public Set<Integer> getSupportedPreambleCodeIndices() {
         Set<Integer> preambles = new HashSet<>();
         try {
@@ -320,6 +329,7 @@
      * @return the timestamp resolution in nanoseconds
      */
     @SuppressLint("MethodNameUnits")
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public long elapsedRealtimeResolutionNanos() {
         try {
             return mUwbAdapter.getTimestampResolutionNanos();
@@ -333,6 +343,7 @@
      *
      * @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxSimultaneousSessions() {
         try {
             return mUwbAdapter.getMaxSimultaneousSessions();
@@ -347,6 +358,7 @@
      *
      * @return the maximum number of remote devices per {@link RangingSession}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxRemoteDevicesPerInitiatorSession() {
         try {
             return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession();
@@ -361,6 +373,7 @@
      *
      * @return the maximum number of remote devices per {@link RangingSession}
      */
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public int getMaxRemoteDevicesPerResponderSession() {
         try {
             return mUwbAdapter.getMaxRemoteDevicesPerResponderSession();
@@ -396,6 +409,7 @@
      *         {@link RangingSession.Callback#onOpened(RangingSession)}.
      */
     @NonNull
+    @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
     public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull RangingSession.Callback callbacks) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6a629ca..66b9617 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2404,7 +2404,7 @@
         if (Float.isNaN(brightness) || brightness > 1.0f
                 || (brightness < 0.0f && brightness != -1.0f)) {
             throw new IllegalArgumentException("brightness must be a number between 0.0f and 1.0f,"
-                    + " or -1 to turn the backlight off.");
+                    + " or -1 to turn the backlight off: " + brightness);
         }
         return nativeSetDisplayBrightness(displayToken, brightness);
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1273b49..44d4d6b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22164,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.
          *
@@ -24028,6 +24032,7 @@
      * @see #getBackgroundTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mBackgroundTint == null) {
             mBackgroundTint = new TintInfo();
@@ -24290,6 +24295,7 @@
      * @see #getForegroundTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setForegroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mForegroundInfo == null) {
             mForegroundInfo = new ForegroundInfo();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3ef6dfb..f1f6786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2841,8 +2841,7 @@
                         mScroller.abortAnimation();
                     }
                     // Our surface is gone
-                    if (mAttachInfo.mThreadedRenderer != null &&
-                            mAttachInfo.mThreadedRenderer.isEnabled()) {
+                    if (isHardwareEnabled()) {
                         mAttachInfo.mThreadedRenderer.destroy();
                     }
                 } else if ((surfaceReplaced
@@ -3928,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;
         }
 
@@ -4273,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;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7b2bb73..a8fff8b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -749,7 +749,7 @@
         @Override
         public boolean hasActiveConnection(View view) {
             synchronized (mH) {
-                if (!hasServedByInputMethodLocked(view)) {
+                if (!hasServedByInputMethodLocked(view) || mCurMethod == null) {
                     return false;
                 }
 
@@ -765,6 +765,17 @@
         return mDelegate;
     }
 
+    /**
+     * Checks whether the active input connection (if any) is for the given view.
+     *
+     * @hide
+     * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+     */
+    @TestApi
+    public boolean hasActiveInputConnection(@Nullable View view) {
+        return mDelegate.hasActiveConnection(view);
+    }
+
     private View getServedViewLocked() {
         return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
     }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 2e75834..bc2b221 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -31,6 +31,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
@@ -261,7 +262,7 @@
             // us honest and minimize usage of WebView internals when binding the proxy.
             if (sProviderInstance != null) return sProviderInstance;
 
-            sTimestamps[WEBVIEW_LOAD_START] = System.currentTimeMillis();
+            sTimestamps[WEBVIEW_LOAD_START] = SystemClock.elapsedRealtime();
             final int uid = android.os.Process.myUid();
             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                     || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -401,7 +402,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
-            sTimestamps[CREATE_CONTEXT_START] = System.currentTimeMillis();
+            sTimestamps[CREATE_CONTEXT_START] = SystemClock.elapsedRealtime();
             try {
                 // Construct an app context to load the Java code into the current app.
                 Context webViewContext = initialApplication.createApplicationContext(
@@ -410,7 +411,7 @@
                 sPackageInfo = newPackageInfo;
                 return webViewContext;
             } finally {
-                sTimestamps[CREATE_CONTEXT_END] = System.currentTimeMillis();
+                sTimestamps[CREATE_CONTEXT_END] = SystemClock.elapsedRealtime();
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -436,26 +437,26 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
-                sTimestamps[ADD_ASSETS_START] = System.currentTimeMillis();
+                sTimestamps[ADD_ASSETS_START] = SystemClock.elapsedRealtime();
                 for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
                     initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
                 }
                 sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 ClassLoader clazzLoader = webViewContext.getClassLoader();
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                 sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                         getWebViewLibrary(sPackageInfo.applicationInfo));
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
                 sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
-                        System.currentTimeMillis();
+                        SystemClock.elapsedRealtime();
                 try {
                     return getWebViewProviderClass(clazzLoader);
                 } finally {
-                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = System.currentTimeMillis();
+                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = SystemClock.elapsedRealtime();
                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 }
             } catch (ClassNotFoundException e) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 681fd14..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -25,8 +25,12 @@
 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;
@@ -149,6 +153,8 @@
     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.
@@ -250,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;
 
@@ -313,6 +326,15 @@
     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;
     }
@@ -466,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;
         }
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/ImageView.java b/core/java/android/widget/ImageView.java
index ed20d26..0a08ccd 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -695,6 +695,7 @@
      * @see #getImageTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
         mDrawableBlendMode = blendMode;
         mHasDrawableBlendMode = true;
diff --git a/core/java/android/widget/ImeAwareEditText.java b/core/java/android/widget/ImeAwareEditText.java
index 9cd4585..0d98085 100644
--- a/core/java/android/widget/ImeAwareEditText.java
+++ b/core/java/android/widget/ImeAwareEditText.java
@@ -80,7 +80,7 @@
 
     public void scheduleShowSoftInput() {
         final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm.isActive(this)) {
+        if (imm.hasActiveInputConnection(this)) {
             // This means that ImeAwareEditText is already connected to the IME.
             // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
             mHasPendingShowSoftInputRequest = false;
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 67216f59..a44808e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -823,6 +823,7 @@
      * @see #setIndeterminateTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1132,6 +1133,7 @@
      * @see #getProgressTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setProgressTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1248,6 +1250,7 @@
      * @see #setProgressBackgroundTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
@@ -1360,6 +1363,7 @@
      * @see #setSecondaryProgressTintList(ColorStateList)
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) {
         if (mProgressTintInfo == null) {
             mProgressTintInfo = new ProgressTintInfo();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6cb4b81..81572b5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,7 +47,9 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.BlendMode;
 import android.graphics.Outline;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -99,6 +101,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;
@@ -171,6 +175,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;
@@ -290,7 +299,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)
@@ -322,6 +331,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
@@ -331,12 +341,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));
@@ -1031,6 +1055,8 @@
                 return ColorStateList.class;
             case BaseReflectionAction.ICON:
                 return Icon.class;
+            case BaseReflectionAction.BLEND_MODE:
+                return BlendMode.class;
             default:
                 return null;
         }
@@ -1377,6 +1403,7 @@
         static final int INTENT = 14;
         static final int COLOR_STATE_LIST = 15;
         static final int ICON = 16;
+        static final int BLEND_MODE = 17;
 
         @UnsupportedAppUsage
         String methodName;
@@ -1566,6 +1593,10 @@
                     break;
                 case ICON:
                     this.value = in.readTypedObject(Icon.CREATOR);
+                    break;
+                case BLEND_MODE:
+                    this.value = BlendMode.fromValue(in.readInt());
+                    break;
                 default:
                     break;
             }
@@ -1609,6 +1640,9 @@
                 case BUNDLE:
                     out.writeBundle((Bundle) this.value);
                     break;
+                case BLEND_MODE:
+                    out.writeInt(BlendMode.toValue((BlendMode) this.value));
+                    break;
                 case URI:
                 case BITMAP:
                 case INTENT:
@@ -2768,23 +2802,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;
@@ -2802,9 +2863,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;
@@ -2812,12 +2948,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);
@@ -2867,10 +3011,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);
@@ -2990,16 +3153,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);
         }
     }
 
@@ -3018,10 +3185,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<>();
@@ -3976,6 +4143,18 @@
     }
 
     /**
+     * Call a method taking one BlendMode on a view in the layout for this RemoteViews.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param value The value to pass to the method.
+     */
+    public void setBlendMode(@IdRes int viewId, @NonNull String methodName,
+            @Nullable BlendMode value) {
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value));
+    }
+
+    /**
      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
@@ -4100,14 +4279,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.
      *
@@ -4124,7 +4368,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);
@@ -4132,9 +4382,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);
@@ -4219,12 +4477,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);
     }
 
@@ -4341,12 +4613,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.");
@@ -4377,12 +4655,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.");
@@ -4466,7 +4750,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.
@@ -4479,9 +4763,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
@@ -4735,11 +5036,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) {
@@ -4754,9 +5053,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/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/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/window/SplashScreenView.aidl b/core/java/android/window/SplashScreenView.aidl
new file mode 100644
index 0000000..cc7ac1e
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+/** @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/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
new file mode 100644
index 0000000..5d02a29
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+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/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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 87820a8..e599888 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -12706,12 +12706,12 @@
         // When the battery is not on, we don't attribute the cpu times to any timers but we still
         // need to take the snapshots.
         if (!onBattery) {
-            mCpuUidUserSysTimeReader.readDelta(null);
-            mCpuUidFreqTimeReader.readDelta(null);
+            mCpuUidUserSysTimeReader.readDelta(false, null);
+            mCpuUidFreqTimeReader.readDelta(false, null);
             mNumAllUidCpuTimeReads += 2;
             if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-                mCpuUidActiveTimeReader.readDelta(null);
-                mCpuUidClusterTimeReader.readDelta(null);
+                mCpuUidActiveTimeReader.readDelta(false, null);
+                mCpuUidClusterTimeReader.readDelta(false, null);
                 mNumAllUidCpuTimeReads += 2;
             }
             for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
@@ -12897,7 +12897,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
 
-        mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> {
+        mCpuUidUserSysTimeReader.readDelta(false, (uid, timesUs) -> {
             long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
 
             uid = mapUid(uid);
@@ -13011,7 +13011,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+        mCpuUidFreqTimeReader.readDelta(false, (uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
@@ -13129,7 +13129,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
+        mCpuUidActiveTimeReader.readDelta(false, (uid, cpuActiveTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
@@ -13163,7 +13163,7 @@
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final List<Integer> uidsToRemove = new ArrayList<>();
-        mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
+        mCpuUidClusterTimeReader.readDelta(false, (uid, cpuClusterTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 uidsToRemove.add(uid);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 45d8128..97f727b 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -124,15 +124,15 @@
         long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
 
         // Constant battery drain when CPU is active
-        double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());
+        double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime());
 
         // Additional per-cluster battery drain
         long[] cpuClusterTimes = u.getCpuClusterTimes();
         if (cpuClusterTimes != null) {
             if (cpuClusterTimes.length == mNumCpuClusters) {
                 for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
-                    double power = mPerClusterPowerEstimators[cluster]
-                            .calculatePower(cpuClusterTimes[cluster]);
+                    double power = calculatePerCpuClusterPowerMah(cluster,
+                            cpuClusterTimes[cluster]);
                     powerMah += power;
                     if (DEBUG) {
                         Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
@@ -151,8 +151,8 @@
             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);
+                final double power = calculatePerCpuFreqPowerMah(cluster, speed,
+                        timeUs / 1000);
                 if (DEBUG) {
                     Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                             + speed + " timeUs=" + timeUs + " power="
@@ -207,4 +207,39 @@
         result.powerMah = powerMah;
         result.packageWithHighestDrain = packageWithHighestDrain;
     }
+
+    /**
+     * Calculates active CPU power consumption.
+     *
+     * @param durationsMs duration of CPU usage.
+     * @return a double in milliamp-hours of estimated active CPU power consumption.
+     */
+    public double calculateActiveCpuPowerMah(long durationsMs) {
+        return mCpuActivePowerEstimator.calculatePower(durationsMs);
+    }
+
+    /**
+     * Calculates CPU cluster power consumption.
+     *
+     * @param cluster CPU cluster used.
+     * @param clusterDurationMs duration of CPU cluster usage.
+     * @return a double in milliamp-hours of estimated CPU cluster power consumption.
+     */
+    public double calculatePerCpuClusterPowerMah(int cluster, long clusterDurationMs) {
+        return mPerClusterPowerEstimators[cluster].calculatePower(clusterDurationMs);
+    }
+
+    /**
+     * Calculates CPU cluster power consumption at a specific speedstep.
+     *
+     * @param cluster CPU cluster used.
+     * @param speedStep which speedstep used.
+     * @param clusterSpeedDurationsMs duration of CPU cluster usage at the specified speed step.
+     * @return a double in milliamp-hours of estimated CPU cluster-speed power consumption.
+     */
+    public double calculatePerCpuFreqPowerMah(int cluster, int speedStep,
+            long clusterSpeedDurationsMs) {
+        return mPerCpuFreqPowerEstimators[cluster][speedStep].calculatePower(
+                clusterSpeedDurationsMs);
+    }
 }
diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
new file mode 100644
index 0000000..2852547
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+/** 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/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index f7fad2c..4299f09 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -91,15 +91,24 @@
      * Reads the proc file, calling into the callback with a delta of time for each UID.
      *
      * @param cb The callback to invoke for each line of the proc file. If null,the data is
-     *           consumed and subsequent calls to readDelta will provide a fresh delta.
      */
     public void readDelta(@Nullable Callback<T> cb) {
+        readDelta(false, cb);
+    }
+
+    /**
+     * Reads the proc file, calling into the callback with a delta of time for each UID.
+     *
+     * @param force Ignore the throttling and force read the delta.
+     * @param cb The callback to invoke for each line of the proc file. If null,the data is
+     */
+    public void readDelta(boolean force, @Nullable Callback<T> cb) {
         if (!mThrottle) {
             readDeltaImpl(cb);
             return;
         }
         final long currTimeMs = SystemClock.elapsedRealtime();
-        if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+        if (!force && currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
             if (DEBUG) {
                 Slog.d(mTag, "Throttle readDelta");
             }
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 95999a7..fe3042d 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -74,6 +74,7 @@
     private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
 
     private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
+    private static final String TAG_TOMBSTONE_PROTO = "SYSTEM_TOMBSTONE_PROTO";
 
     // The pre-froyo package and class of the system updater, which
     // ran in the system process.  We need to remove its packages here
@@ -251,14 +252,14 @@
      * @param ctx Context
      * @param tombstone path to the tombstone
      */
-    public static void addTombstoneToDropBox(Context ctx, File tombstone) {
+    public static void addTombstoneToDropBox(Context ctx, File tombstone, boolean proto) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
         HashMap<String, Long> timestamps = readTimestamps();
         try {
             final String headers = getBootHeadersToLogAndUpdate();
             addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
-                    TAG_TOMBSTONE);
+                    proto ? TAG_TOMBSTONE_PROTO : TAG_TOMBSTONE);
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
         }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9510f27..d72a0ec 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,11 +49,12 @@
         "libhwui",
         "liblog",
         "libminikin",
-        "libnativehelper",
         "libz",
         "libziparchive",
     ],
 
+    static_libs: ["libnativehelper_lazy"],
+
     export_include_dirs: [
         ".",
         "include",
@@ -184,6 +185,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",
@@ -271,12 +273,13 @@
                 "libstatspull",
             ],
             export_shared_lib_headers: [
-                // AndroidRuntime.h depends on nativehelper/jni.h
-                "libnativehelper",
-
                 // our headers include libnativewindow's public headers
                 "libnativewindow",
             ],
+            export_static_lib_headers: [
+                // AndroidRuntime.h depends on nativehelper/jni.h
+                "libnativehelper_lazy",
+            ],
             header_libs: [
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
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_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index a7950f2..8dcb210 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -829,6 +829,16 @@
     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;
@@ -986,6 +996,8 @@
             (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 },
     { "getDmabufMappedSizeKb", "()J",
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/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 ba43ee7..c61802d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1313,11 +1313,13 @@
         android:protectionLevel="dangerous|instant" />
 
     <!-- ====================================================================== -->
-    <!-- Permissions for accessing the UCE Service                              -->
+    <!-- Permissions for accessing the vendor UCE Service                              -->
     <!-- ====================================================================== -->
 
     <!-- @hide Allows an application to Access UCE-Presence.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1325,6 +1327,8 @@
 
     <!-- @hide Allows an application to Access UCE-OPTIONS.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1403,6 +1407,15 @@
         android:description="@string/permgroupdesc_sensors"
         android:priority="800" />
 
+    <!-- Allows an app to access sensor data with a sampling rate greater than 200 Hz.
+        <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"
+                android:permissionGroup="android.permission-group.SENSORS"
+                android:label="@string/permlab_highSamplingRateSensors"
+                android:description="@string/permdesc_highSamplingRateSensors"
+                android:protectionLevel="normal" />
+
     <!-- Allows an application to access data from sensors that the user uses to
          measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
@@ -2002,6 +2015,12 @@
     <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to ultra wideband device.
+     <p>Not for use by third-party applications.
+     @hide -->
+    <permission android:name="android.permission.UWB_PRIVILEGED"
+                android:protectionLevel="signature|privileged" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -2263,6 +2282,11 @@
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows read access to privileged network state in the device config.
+         @hide Used internally. -->
+    <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
          Often required in authentication to access the carrier's server and manage services
          of the subscriber.
@@ -2448,6 +2472,15 @@
     <permission android:name="android.permission.BIND_GBA_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+         <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+         Contacts app roles.
+         <p>Protection level: internal|role
+         @SystemApi
+         @hide -->
+    <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+        android:protectionLevel="internal|role" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -3872,6 +3905,12 @@
     <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows an app to use exact alarm scheduling APIs to perform timing
+         sensitive background work.
+     -->
+    <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+        android:protectionLevel="normal|appop"/>
+
     <!-- Allows an application to query tablet mode state and monitor changes
          in it.
          <p>Not for use by third-party applications.
@@ -4372,6 +4411,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"
@@ -5477,6 +5522,15 @@
     <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" />
+
+    <!-- @hide @SystemApi Allows an application to retrieve whether shortcut is backed by a
+         Conversation.
+         TODO(b/180412052): STOPSHIP: Define a role so it can be granted to Shell and AiAi. -->
+    <permission android:name="android.permission.READ_PEOPLE_DATA"
+                android:protectionLevel="signature|appPredictor|recents"/>
 
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
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_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/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/attrs.xml b/core/res/res/values/attrs.xml
index 823e10e..69bb20c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2262,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. -->
@@ -6372,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">
@@ -7986,6 +8010,14 @@
         <attr name="minResizeWidth" format="dimension"/>
         <!-- Minimum height that the AppWidget can be resized to. -->
         <attr name="minResizeHeight" format="dimension"/>
+        <!-- Maximum width that the AppWidget can be resized to. -->
+        <attr name="maxResizeWidth" format="dimension"/>
+        <!-- Maximum height that the AppWidget can be resized to. -->
+        <attr name="maxResizeHeight" format="dimension"/>
+        <!-- Default width of the AppWidget in units of launcher grid cells. -->
+        <attr name="targetCellWidth" format="integer"/>
+        <!-- Default height of the AppWidget in units of launcher grid cells. -->
+        <attr name="targetCellHeight" format="integer"/>
         <!-- Update period in milliseconds, or 0 if the AppWidget will update itself. -->
         <attr name="updatePeriodMillis" format="integer" />
         <!-- A resource id of a layout. -->
@@ -9260,6 +9292,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/colors.xml b/core/res/res/values/colors.xml
index 5546621..59c260c 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -240,77 +240,114 @@
 
     <color name="conversation_important_highlight">#F9AB00</color>
 
-    <!-- Lightest shade of the main color used by the system. White.
+    <!-- Lightest shade of the primary color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_0">#ffffff</color>
-    <!-- Shade of the main system color at 95% lightness.
+    <color name="system_primary_0">#ffffff</color>
+    <!-- Shade of the primary system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_50">#f0f0f0</color>
-    <!-- Shade of the main system color at 90% lightness.
+    <color name="system_primary_50">#f2f2f2</color>
+    <!-- Shade of the primary system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_100">#e2e2e2</color>
-    <!-- Shade of the main system color at 80% lightness.
+    <color name="system_primary_100">#e3e3e3</color>
+    <!-- Shade of the primary system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_200">#c6c6c6</color>
-    <!-- Shade of the main system color at 70% lightness.
+    <color name="system_primary_200">#c7c7c7</color>
+    <!-- Shade of the primary system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_300">#ababab</color>
-    <!-- Shade of the main system color at 60% lightness.
+    <color name="system_primary_300">#ababab</color>
+    <!-- Shade of the primary system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_400">#909090</color>
-    <!-- Shade of the main system color at 50% lightness.
+    <color name="system_primary_400">#8f8f8f</color>
+    <!-- Shade of the primary system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_500">#757575</color>
-    <!-- Shade of the main system color at 40% lightness.
+    <color name="system_primary_500">#757575</color>
+    <!-- Shade of the primary system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_600">#5e5e5e</color>
-    <!-- Shade of the main system color at 30% lightness.
+    <color name="system_primary_600">#5e5e5e</color>
+    <!-- Shade of the primary system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_700">#464646</color>
-    <!-- Shade of the main system color at 20% lightness.
+    <color name="system_primary_700">#474747</color>
+    <!-- Shade of the primary system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_800">#303030</color>
-    <!-- Shade of the main system color at 10% lightness.
+    <color name="system_primary_800">#303030</color>
+    <!-- Shade of the primary system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_900">#1b1b1b</color>
-    <!-- Darkest shade of the main color used by the system. Black.
+    <color name="system_primary_900">#1f1f1f</color>
+    <!-- Darkest shade of the primary color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_1000">#000000</color>
+    <color name="system_primary_1000">#000000</color>
 
-    <!-- Lightest shade of the accent color used by the system. White.
+    <!-- Lightest shade of the secondary color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_0">#ffffff</color>
-    <!-- Shade of the accent system color at 95% lightness.
+    <color name="system_secondary_0">#ffffff</color>
+    <!-- Shade of the secondary system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_50">#91fff4</color>
-    <!-- Shade of the accent system color at 90% lightness.
+    <color name="system_secondary_50">#91fff4</color>
+    <!-- Shade of the secondary system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_100">#83f6e5</color>
-    <!-- Shade of the accent system color at 80% lightness.
+    <color name="system_secondary_100">#83f6e5</color>
+    <!-- Shade of the secondary system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_200">#65d9c9</color>
-    <!-- Shade of the accent system color at 70% lightness.
+    <color name="system_secondary_200">#65d9c9</color>
+    <!-- Shade of the secondary system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_300">#45bdae</color>
-    <!-- Shade of the accent system color at 60% lightness.
+    <color name="system_secondary_300">#45bdae</color>
+    <!-- Shade of the secondary system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_400">#1fa293</color>
-    <!-- Shade of the accent system color at 50% lightness.
+    <color name="system_secondary_400">#1fa293</color>
+    <!-- Shade of the secondary system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_500">#008377</color>
-    <!-- Shade of the accent system color at 40% lightness.
+    <color name="system_secondary_500">#008377</color>
+    <!-- Shade of the secondary system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_600">#006d61</color>
-    <!-- Shade of the accent system color at 30% lightness.
+    <color name="system_secondary_600">#006d61</color>
+    <!-- Shade of the secondary system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_700">#005449</color>
-    <!-- Shade of the accent system color at 20% lightness.
+    <color name="system_secondary_700">#005449</color>
+    <!-- Shade of the secondary system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_800">#003c33</color>
-    <!-- Shade of the accent system color at 10% lightness.
+    <color name="system_secondary_800">#003c33</color>
+    <!-- Shade of the secondary system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_900">#00271e</color>
-    <!-- Darkest shade of the accent color used by the system. Black.
+    <color name="system_secondary_900">#00271e</color>
+    <!-- Darkest shade of the secondary color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_1000">#000000</color>
+    <color name="system_secondary_1000">#000000</color>
+
+    <!-- Lightest shade of the neutral color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_0">#ffffff</color>
+    <!-- Shade of the neutral system color at 95% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_50">#f0f0f0</color>
+    <!-- Shade of the neutral system color at 90% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_100">#e2e2e2</color>
+    <!-- Shade of the neutral system color at 80% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_200">#c6c6c6</color>
+    <!-- Shade of the neutral system color at 70% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_300">#ababab</color>
+    <!-- Shade of the neutral system color at 60% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_400">#909090</color>
+    <!-- Shade of the neutral system color at 50% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_500">#757575</color>
+    <!-- Shade of the neutral system color at 40% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_600">#5e5e5e</color>
+    <!-- Shade of the neutral system color at 30% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_700">#464646</color>
+    <!-- Shade of the neutral system color at 20% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_800">#303030</color>
+    <!-- Shade of the neutral system color at 10% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_900">#1b1b1b</color>
+    <!-- Darkest shade of the neutral color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral_1000">#000000</color>
 </resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index f723426..1020c14 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
 <!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
      overlaying new theme colors. -->
 <resources>
-    <color name="primary_device_default_dark">@color/system_main_800</color>
-    <color name="primary_device_default_light">@color/system_main_50</color>
-    <color name="primary_device_default_settings">@color/system_main_800</color>
+    <color name="primary_device_default_dark">@color/system_primary_800</color>
+    <color name="primary_device_default_light">@color/system_primary_50</color>
+    <color name="primary_device_default_settings">@color/system_primary_800</color>
     <color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
     <color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
     <color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -33,21 +33,21 @@
     <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
     <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
 
-    <color name="accent_device_default_light">@color/system_accent_600</color>
-    <color name="accent_device_default_dark">@color/system_accent_200</color>
+    <color name="accent_device_default_light">@color/system_secondary_600</color>
+    <color name="accent_device_default_dark">@color/system_secondary_200</color>
     <color name="accent_device_default">@color/accent_device_default_light</color>
 
-    <color name="background_device_default_dark">@color/system_main_800</color>
-    <color name="background_device_default_light">@color/system_main_50</color>
-    <color name="background_floating_device_default_dark">@color/system_main_900</color>
-    <color name="background_floating_device_default_light">@color/system_main_100</color>
+    <color name="background_device_default_dark">@color/system_primary_800</color>
+    <color name="background_device_default_light">@color/system_primary_50</color>
+    <color name="background_floating_device_default_dark">@color/system_primary_900</color>
+    <color name="background_floating_device_default_light">@color/system_primary_100</color>
 
-    <color name="text_color_primary_device_default_light">@color/system_main_900</color>
-    <color name="text_color_primary_device_default_dark">@color/system_main_50</color>
-    <color name="text_color_secondary_device_default_light">@color/system_main_700</color>
-    <color name="text_color_secondary_device_default_dark">@color/system_main_200</color>
-    <color name="text_color_tertiary_device_default_light">@color/system_main_500</color>
-    <color name="text_color_tertiary_device_default_dark">@color/system_main_400</color>
+    <color name="text_color_primary_device_default_light">@color/system_primary_900</color>
+    <color name="text_color_primary_device_default_dark">@color/system_primary_50</color>
+    <color name="text_color_secondary_device_default_light">@color/system_primary_700</color>
+    <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color>
+    <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color>
+    <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color>
     <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
     <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color>
 
@@ -55,8 +55,8 @@
     <color name="error_color_device_default_dark">@color/error_color_material_dark</color>
     <color name="error_color_device_default_light">@color/error_color_material_light</color>
 
-    <color name="list_divider_color_light">@color/system_main_500</color>
-    <color name="list_divider_color_dark">@color/system_main_400</color>
+    <color name="list_divider_color_light">@color/system_primary_500</color>
+    <color name="list_divider_color_dark">@color/system_primary_400</color>
     <color name="list_divider_opacity_device_default_light">@android:color/white</color>
     <color name="list_divider_opacity_device_default_dark">@android:color/white</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b7c755e..faa2157 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,6 +1946,10 @@
     <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 shell role. -->
+    <string name="config_systemShell" translatable="false">com.android.shell</string>
+    <!-- The name of the package that will hold the system contacts role. -->
+    <string name="config_systemContacts" translatable="false">com.android.contacts</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -2507,10 +2511,9 @@
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
 
     <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
-         Default is carrier_config, but it should be set to modem if the modem is returning
-         predictive (instead of instantaneous) bandwidth estimate.
-         Values are carrier_config and modem. -->
-    <string name="config_bandwidthEstimateSource">carrier_config</string>
+         Default is bandwidth_estimator.
+         Values are bandwidth_estimator, carrier_config and modem. -->
+    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
 
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index debdab0..7066419 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -926,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/public.xml b/core/res/res/values/public.xml
index 3c97aec..22dce9b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3066,6 +3066,16 @@
     <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 name="maxResizeWidth" />
+    <public name="maxResizeHeight" />
+    <public name="targetCellWidth" />
+    <public name="targetCellHeight" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3076,36 +3086,54 @@
     <!-- color definitions go here -->
 
     <!-- Material design dynamic system palette:-->
-    <!-- Dominant color -->
-    <public name="system_main_0" />
-    <public name="system_main_50" />
-    <public name="system_main_100" />
-    <public name="system_main_200" />
-    <public name="system_main_300" />
-    <public name="system_main_400" />
-    <public name="system_main_500" />
-    <public name="system_main_600" />
-    <public name="system_main_700" />
-    <public name="system_main_800" />
-    <public name="system_main_900" />
-    <public name="system_main_1000" />
-    <!-- Accent color -->
-    <public name="system_accent_0" />
-    <public name="system_accent_50" />
-    <public name="system_accent_100" />
-    <public name="system_accent_200" />
-    <public name="system_accent_300" />
-    <public name="system_accent_400" />
-    <public name="system_accent_500" />
-    <public name="system_accent_600" />
-    <public name="system_accent_700" />
-    <public name="system_accent_800" />
-    <public name="system_accent_900" />
-    <public name="system_accent_1000" />
+    <!-- Primary color -->
+    <public name="system_primary_0" />
+    <public name="system_primary_50" />
+    <public name="system_primary_100" />
+    <public name="system_primary_200" />
+    <public name="system_primary_300" />
+    <public name="system_primary_400" />
+    <public name="system_primary_500" />
+    <public name="system_primary_600" />
+    <public name="system_primary_700" />
+    <public name="system_primary_800" />
+    <public name="system_primary_900" />
+    <public name="system_primary_1000" />
+    <!-- Secondary color -->
+    <public name="system_secondary_0" />
+    <public name="system_secondary_50" />
+    <public name="system_secondary_100" />
+    <public name="system_secondary_200" />
+    <public name="system_secondary_300" />
+    <public name="system_secondary_400" />
+    <public name="system_secondary_500" />
+    <public name="system_secondary_600" />
+    <public name="system_secondary_700" />
+    <public name="system_secondary_800" />
+    <public name="system_secondary_900" />
+    <public name="system_secondary_1000" />
+    <!-- Neutral color -->
+    <public name="system_neutral_0" />
+    <public name="system_neutral_50" />
+    <public name="system_neutral_100" />
+    <public name="system_neutral_200" />
+    <public name="system_neutral_300" />
+    <public name="system_neutral_400" />
+    <public name="system_neutral_500" />
+    <public name="system_neutral_600" />
+    <public name="system_neutral_700" />
+    <public name="system_neutral_800" />
+    <public name="system_neutral_900" />
+    <public name="system_neutral_1000" />
   </public-group>
 
   <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">
@@ -3121,6 +3149,10 @@
     <public name="config_systemAutomotiveCluster" />
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveProjection" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemShell" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemContacts" />
   </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 af5e406..9ebe23a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1830,6 +1830,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_highSamplingRateSensors">access sensor data at a high sampling rate</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+    <string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4109d4c..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" />
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/tests/coretests/src/android/app/people/PeopleManagerTest.java b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java
new file mode 100644
index 0000000..a2afc77
--- /dev/null
+++ b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.people;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+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 android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link android.app.people.PeopleManager.ConversationListener} and relevant APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PeopleManagerTest {
+
+    private static final String CONVERSATION_ID_1 = "12";
+    private static final String CONVERSATION_ID_2 = "123";
+
+    private Context mContext;
+
+    private final TestLooper mTestLooper = new TestLooper();
+
+    @Mock
+    private IPeopleManager mService;
+    private PeopleManager mPeopleManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        MockitoAnnotations.initMocks(this);
+
+        mPeopleManager = new PeopleManager(mContext, mService);
+    }
+
+    @Test
+    public void testCorrectlyMapsToProxyConversationListener() throws Exception {
+        PeopleManager.ConversationListener listenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, listenerForConversation1);
+        PeopleManager.ConversationListener listenerForConversation2 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_2, listenerForConversation2);
+
+        Map<PeopleManager.ConversationListener, Pair<Executor, IConversationListener>>
+                listenersToProxy =
+                mPeopleManager.mConversationListeners;
+        Pair<Executor, IConversationListener> listener = listenersToProxy.get(
+                listenerForConversation1);
+        ConversationChannel conversation = getConversation(CONVERSATION_ID_1);
+        listener.second.onConversationUpdate(getConversation(CONVERSATION_ID_1));
+        mTestLooper.dispatchAll();
+
+        // Only call the associated listener.
+        verify(listenerForConversation2, never()).onConversationUpdate(any());
+        // Should update the listeners mapped to the proxy.
+        ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
+                ConversationChannel.class);
+        verify(listenerForConversation1, times(1)).onConversationUpdate(
+                capturedConversation.capture());
+        ConversationChannel conversationChannel = capturedConversation.getValue();
+        assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
+        assertEquals(conversationChannel.getShortcutInfo().getLabel(),
+                conversation.getShortcutInfo().getLabel());
+    }
+
+    private ConversationChannel getConversation(String shortcutId) {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
+                shortcutId).setLongLabel(
+                "name").build();
+        NotificationChannel notificationChannel = new NotificationChannel("123",
+                "channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        return new ConversationChannel(shortcutInfo, 0,
+                notificationChannel, null,
+                123L, false);
+    }
+
+    private void registerListener(String conversationId,
+            PeopleManager.ConversationListener listener) {
+        mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
+                conversationId, listener,
+                mTestLooper.getNewExecutor());
+    }
+}
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/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
index be38260..bc7be1b 100644
--- a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -16,14 +16,8 @@
 
 package android.provider;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.testng.Assert.assertThrows;
 
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
-
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Test;
@@ -71,50 +65,5 @@
                         SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
         );
     }
-
-    @Test
-    public void nameValidationResult_isValid_validNames() {
-        assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
-        assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
-        assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
-        assertThat(
-                new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
-    }
-
-    @Test
-    public void nameValidationResult_isValid_invalidNames() {
-        assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
-        assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
-        NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
-                "A b c", 5, 5);
-        assertThat(unsupportedCharactersResult.isValid()).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
-        assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
-    }
-
-    @Test
-    public void nameValidationResult_parcel() {
-        ContentValues values = new ContentValues();
-        values.put("name", "Name");
-        values.put("phone_number", "123");
-
-        NameValidationResult result;
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
-            parcel.setDataPosition(0);
-            result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
-        } finally {
-            parcel.recycle();
-        }
-
-        assertThat(result.getName()).isEqualTo("name");
-        assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
-        assertThat(result.getEncodedLength()).isEqualTo(1);
-        assertThat(result.getMaxEncodedLength()).isEqualTo(2);
-    }
 }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 8f81ea2..7dca0cb 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -93,35 +93,62 @@
         mReader.setThrottle(500);
 
         writeToFile(uidLines(mUids, mInitialTimes));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(6, mCallback.mData.size());
 
         long[][] times1 = increaseTime(mInitialTimes);
         writeToFile(uidLines(mUids, times1));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(0, mCallback.mData.size());
 
+        // TODO(b/180473895): Replace sleeps with injected simulated time.
         SystemClock.sleep(600);
 
         long[][] times2 = increaseTime(times1);
         writeToFile(uidLines(mUids, times2));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(6, mCallback.mData.size());
 
         long[][] times3 = increaseTime(times2);
         writeToFile(uidLines(mUids, times3));
         mCallback.clear();
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         assertEquals(0, mCallback.mData.size());
+
+        // Force the delta read, previously skipped increments should now be read
+        mCallback.clear();
+        mReader.readDelta(true, mCallback);
+        assertEquals(6, mCallback.mData.size());
+
+        SystemClock.sleep(600);
+
+        long[][] times4 = increaseTime(times3);
+        writeToFile(uidLines(mUids, times4));
+        mCallback.clear();
+        mReader.readDelta(true, mCallback);
+        assertEquals(6, mCallback.mData.size());
+
+        // Don't force the delta read, throttle should be set from last read.
+        long[][] times5 = increaseTime(times4);
+        writeToFile(uidLines(mUids, times5));
+        mCallback.clear();
+        mReader.readDelta(false, mCallback);
+        assertEquals(0, mCallback.mData.size());
+
+        SystemClock.sleep(600);
+
+        mCallback.clear();
+        mReader.readDelta(false, mCallback);
+        assertEquals(6, mCallback.mData.size());
     }
 
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times1 = mInitialTimes;
         writeToFile(uidLines(mUids, times1));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
         }
@@ -131,7 +158,7 @@
         // Verify that a second call will only return deltas.
         final long[][] times2 = increaseTime(times1);
         writeToFile(uidLines(mUids, times2));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
         }
@@ -139,20 +166,20 @@
         mCallback.clear();
 
         // Verify that there won't be a callback if the proc file values didn't change.
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         mCallback.verifyNoMoreInteractions();
         mCallback.clear();
 
         // Verify that calling with a null callback doesn't result in any crashes
         final long[][] times3 = increaseTime(times2);
         writeToFile(uidLines(mUids, times3));
-        mReader.readDelta(null);
+        mReader.readDelta(false, null);
 
         // Verify that the readDelta call will only return deltas when
         // the previous call had null callback.
         final long[][] times4 = increaseTime(times3);
         writeToFile(uidLines(mUids, times4));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
         }
@@ -165,7 +192,7 @@
     public void testReadDeltaWrongData() throws Exception {
         final long[][] times1 = mInitialTimes;
         writeToFile(uidLines(mUids, times1));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
         }
@@ -176,7 +203,7 @@
         final long[][] times2 = increaseTime(times1);
         times2[0][0] = 1000;
         writeToFile(uidLines(mUids, times2));
-        mReader.readDelta(mCallback);
+        mReader.readDelta(false, mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
         }
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index fa92b6d..2d6ae2e 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -21,5 +21,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
+        <!-- Required to update emergency gesture settings -->
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 403dc65..8fd5d80 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"/>
@@ -476,6 +478,8 @@
         <permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
         <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
         <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
+        <!-- Permission required for CTS test - PeopleManagerTest -->
+        <permission name="android.permission.READ_PEOPLE_DATA" />
     </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 69c30f3..5633de3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,6 +259,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1834214907": {
+      "message": "createNonAppWindowAnimations()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "-1824578273": {
       "message": "Reporting new frame to %s: %s",
       "level": "VERBOSE",
@@ -811,6 +817,18 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "-1153814764": {
+      "message": "onAnimationCancelled",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.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",
@@ -889,12 +907,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",
@@ -1081,12 +1093,6 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
-    "-856025122": {
-      "message": "SURFACE transparentRegionHint=%s: %s",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-855366859": {
       "message": "        merging children in from %s: %s",
       "level": "VERBOSE",
@@ -1675,6 +1681,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",
@@ -2821,12 +2833,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1217926207": {
-      "message": "Activity not running, resuming next.",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1219600119": {
       "message": "addWindow: win=%s Callers=%s",
       "level": "DEBUG",
@@ -3331,6 +3337,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "1847414670": {
+      "message": "Activity not running or entered PiP, resuming next.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "1853793312": {
       "message": "Notify removed startingWindow %s",
       "level": "VERBOSE",
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/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/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index e92eaca..c79c12c 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -236,10 +236,51 @@
  * 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 {
-
-    private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
+    private static final X500Principal DEFAULT_CERT_SUBJECT =
+            new X500Principal("CN=Android Keystore Key");
     private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
     private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
@@ -276,6 +317,7 @@
     private final boolean mUnlockedDeviceRequired;
     private final boolean mCriticalToDeviceEncryption;
     private final int mMaxUsageCount;
+    private final String mAttestKeyAlias;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -317,7 +359,8 @@
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
             boolean criticalToDeviceEncryption,
-            int maxUsageCount) {
+            int maxUsageCount,
+            String attestKeyAlias) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -372,6 +415,7 @@
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mCriticalToDeviceEncryption = criticalToDeviceEncryption;
         mMaxUsageCount = maxUsageCount;
+        mAttestKeyAlias = attestKeyAlias;
     }
 
     /**
@@ -828,6 +872,18 @@
     }
 
     /**
+     * Returns the alias of the attestation key that will be used to sign the attestation
+     * certificate of the generated key.  Note that an attestation certificate will only be
+     * generated if an attestation challenge is set.
+     *
+     * @see Builder#setAttestKeyAlias(String)
+     */
+    @Nullable
+    public String getAttestKeyAlias() {
+        return mAttestKeyAlias;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -865,6 +921,7 @@
         private boolean mUnlockedDeviceRequired = false;
         private boolean mCriticalToDeviceEncryption = false;
         private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -934,6 +991,7 @@
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
             mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
             mMaxUsageCount = sourceSpec.getMaxUsageCount();
+            mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
         }
 
         /**
@@ -1654,6 +1712,28 @@
         }
 
         /**
+         * Sets the alias of the attestation key that will be used to sign the attestation
+         * certificate for the generated key pair, if an attestation challenge is set with {@link
+         * #setAttestationChallenge}.  If an attestKeyAlias is set but no challenge, {@link
+         * java.security.KeyPairGenerator#initialize} will throw {@link
+         * java.security.InvalidAlgorithmParameterException}.
+         *
+         * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an
+         * appropriate system-provided attestation signing key.  If not null, the alias must
+         * reference an Android Keystore Key that was created with {@link
+         * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw
+         * {@link java.security.InvalidAlgorithmParameterException}.
+         *
+         * @param attestKeyAlias the alias of the attestation key to be used to sign the
+         *        attestation certificate.
+         */
+        @NonNull
+        public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) {
+            mAttestKeyAlias = attestKeyAlias;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1690,7 +1770,8 @@
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
                     mCriticalToDeviceEncryption,
-                    mMaxUsageCount);
+                    mMaxUsageCount,
+                    mAttestKeyAlias);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 3ebca6a..7b0fa91 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -70,6 +70,7 @@
             PURPOSE_VERIFY,
             PURPOSE_WRAP_KEY,
             PURPOSE_AGREE_KEY,
+            PURPOSE_ATTEST_KEY,
     })
     public @interface PurposeEnum {}
 
@@ -100,10 +101,26 @@
 
     /**
      * 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;
 
     /**
+     * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning
+     * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In
+     * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys.
+     */
+    public static final int PURPOSE_ATTEST_KEY = 1 << 7;
+
+    /**
      * @hide
      */
     public static abstract class Purpose {
@@ -123,6 +140,8 @@
                     return KeymasterDefs.KM_PURPOSE_WRAP;
                 case PURPOSE_AGREE_KEY:
                     return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
+                case PURPOSE_ATTEST_KEY:
+                    return KeymasterDefs.KM_PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
@@ -142,6 +161,8 @@
                     return PURPOSE_WRAP_KEY;
                 case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
                     return PURPOSE_AGREE_KEY;
+                case KeymasterDefs.KM_PURPOSE_ATTEST_KEY:
+                    return PURPOSE_ATTEST_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index aaa3715..fe92270 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -588,7 +588,8 @@
         private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
         private boolean mCriticalToDeviceEncryption = false;
         private boolean mIsStrongBoxBacked = false;
-        private  int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+        private String mAttestKeyAlias = null;
 
         /**
          * Creates a new instance of the {@code Builder}.
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 1f2f853..c20cf01 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -110,6 +110,7 @@
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
         out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
         out.writeInt(mSpec.getMaxUsageCount());
+        out.writeString(mSpec.getAttestKeyAlias());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -170,6 +171,7 @@
         final boolean unlockedDeviceRequired = in.readBoolean();
         final boolean criticalToDeviceEncryption = in.readBoolean();
         final int maxUsageCount = in.readInt();
+        final String attestKeyAlias = in.readString();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -205,7 +207,8 @@
                 userConfirmationRequired,
                 unlockedDeviceRequired,
                 criticalToDeviceEncryption,
-                maxUsageCount);
+                maxUsageCount,
+                attestKeyAlias);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 4d27c34..b3bfd6a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,9 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.KeyPurpose;
 import android.hardware.security.keymint.SecurityLevel;
+import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
@@ -37,9 +39,11 @@
 import android.security.keystore.KeymasterUtils;
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.StrongBoxUnavailableException;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreSecurityLevel;
 import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
 import android.telephony.TelephonyManager;
@@ -61,6 +65,7 @@
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -69,6 +74,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Provides a way to create instances of a KeyPair which will be placed in the
@@ -153,6 +159,7 @@
     private int mKeymasterAlgorithm = -1;
     private int mKeySizeBits;
     private SecureRandom mRng;
+    private KeyDescriptor mAttestKeyDescriptor;
 
     private int[] mKeymasterPurposes;
     private int[] mKeymasterBlockModes;
@@ -197,83 +204,9 @@
                 // Legacy/deprecated spec
                 KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
                 try {
-                    KeyGenParameterSpec.Builder specBuilder;
-                    String specKeyAlgorithm = legacySpec.getKeyType();
-                    if (specKeyAlgorithm != null) {
-                        // Spec overrides the generator's default key algorithm
-                        try {
-                            keymasterAlgorithm =
-                                    KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
-                                            specKeyAlgorithm);
-                        } catch (IllegalArgumentException e) {
-                            throw new InvalidAlgorithmParameterException(
-                                    "Invalid key type in parameters", e);
-                        }
-                    }
-                    switch (keymasterAlgorithm) {
-                        case KeymasterDefs.KM_ALGORITHM_EC:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            // MD5 was never offered for Android Keystore for ECDSA.
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            break;
-                        case KeymasterDefs.KM_ALGORITHM_RSA:
-                            specBuilder = new KeyGenParameterSpec.Builder(
-                                    legacySpec.getKeystoreAlias(),
-                                    KeyProperties.PURPOSE_ENCRYPT
-                                    | KeyProperties.PURPOSE_DECRYPT
-                                    | KeyProperties.PURPOSE_SIGN
-                                    | KeyProperties.PURPOSE_VERIFY);
-                            // Authorized to be used with any digest (including no digest).
-                            specBuilder.setDigests(
-                                    KeyProperties.DIGEST_NONE,
-                                    KeyProperties.DIGEST_MD5,
-                                    KeyProperties.DIGEST_SHA1,
-                                    KeyProperties.DIGEST_SHA224,
-                                    KeyProperties.DIGEST_SHA256,
-                                    KeyProperties.DIGEST_SHA384,
-                                    KeyProperties.DIGEST_SHA512);
-                            // Authorized to be used with any encryption and signature padding
-                            // schemes (including no padding).
-                            specBuilder.setEncryptionPaddings(
-                                    KeyProperties.ENCRYPTION_PADDING_NONE,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
-                                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
-                            specBuilder.setSignaturePaddings(
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
-                                    KeyProperties.SIGNATURE_PADDING_RSA_PSS);
-                            // Disable randomized encryption requirement to support encryption
-                            // padding NONE above.
-                            specBuilder.setRandomizedEncryptionRequired(false);
-                            break;
-                        default:
-                            throw new ProviderException(
-                                    "Unsupported algorithm: " + mKeymasterAlgorithm);
-                    }
-
-                    if (legacySpec.getKeySize() != -1) {
-                        specBuilder.setKeySize(legacySpec.getKeySize());
-                    }
-                    if (legacySpec.getAlgorithmParameterSpec() != null) {
-                        specBuilder.setAlgorithmParameterSpec(
-                                legacySpec.getAlgorithmParameterSpec());
-                    }
-                    specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
-                    specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
-                    specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
-                    specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
-                    specBuilder.setUserAuthenticationRequired(false);
-
-                    spec = specBuilder.build();
+                    keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm,
+                            legacySpec);
+                    spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm);
                 } catch (NullPointerException | IllegalArgumentException e) {
                     throw new InvalidAlgorithmParameterException(e);
                 }
@@ -342,6 +275,10 @@
             mJcaKeyAlgorithm = jcaKeyAlgorithm;
             mRng = random;
             mKeyStore = KeyStore2.getInstance();
+
+            mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
+            checkAttestKeyPurpose(spec);
+
             success = true;
         } finally {
             if (!success) {
@@ -350,6 +287,156 @@
         }
     }
 
+    private void checkAttestKeyPurpose(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0
+                && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) {
+            throw new InvalidAlgorithmParameterException(
+                    "PURPOSE_ATTEST_KEY may not be specified with any other purposes");
+        }
+    }
+
+    private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestKeyAlias() != null) {
+            KeyDescriptor attestKeyDescriptor = new KeyDescriptor();
+            attestKeyDescriptor.domain = Domain.APP;
+            attestKeyDescriptor.alias = spec.getAttestKeyAlias();
+            try {
+                KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor);
+                checkAttestKeyChallenge(spec);
+                checkAttestKeyPurpose(attestKey.metadata.authorizations);
+                checkAttestKeySecurityLevel(spec, attestKey);
+            } catch (KeyStoreException e) {
+                throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e);
+            }
+            return attestKeyDescriptor;
+        }
+        return null;
+    }
+
+    private void checkAttestKeyChallenge(KeyGenParameterSpec spec)
+            throws InvalidAlgorithmParameterException {
+        if (spec.getAttestationChallenge() == null) {
+            throw new InvalidAlgorithmParameterException(
+                    "AttestKey specified but no attestation challenge provided");
+        }
+    }
+
+    private void checkAttestKeyPurpose(Authorization[] keyAuths)
+            throws InvalidAlgorithmParameterException {
+        Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE
+                && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY;
+
+        if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) {
+            throw new InvalidAlgorithmParameterException(
+                    ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+        }
+    }
+
+    private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key)
+            throws InvalidAlgorithmParameterException {
+        boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX;
+        if (spec.isStrongBoxBacked() != attestKeyInStrongBox) {
+            if (attestKeyInStrongBox) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign non-StrongBox key with "
+                                + "StrongBox attestKey");
+
+            } else {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid security level: Cannot sign StrongBox key with "
+                                + "non-StrongBox attestKey");
+            }
+        }
+    }
+
+    private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm,
+            KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException {
+        String specKeyAlgorithm = legacySpec.getKeyType();
+        if (specKeyAlgorithm != null) {
+            // Spec overrides the generator's default key algorithm
+            try {
+                keymasterAlgorithm =
+                        KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+                                specKeyAlgorithm);
+            } catch (IllegalArgumentException e) {
+                throw new InvalidAlgorithmParameterException(
+                        "Invalid key type in parameters", e);
+            }
+        }
+        return keymasterAlgorithm;
+    }
+
+    private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec,
+            int keymasterAlgorithm) {
+        KeyGenParameterSpec.Builder specBuilder;
+        switch (keymasterAlgorithm) {
+            case KeymasterDefs.KM_ALGORITHM_EC:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                // MD5 was never offered for Android Keystore for ECDSA.
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                break;
+            case KeymasterDefs.KM_ALGORITHM_RSA:
+                specBuilder = new KeyGenParameterSpec.Builder(
+                        legacySpec.getKeystoreAlias(),
+                        KeyProperties.PURPOSE_ENCRYPT
+                        | KeyProperties.PURPOSE_DECRYPT
+                        | KeyProperties.PURPOSE_SIGN
+                        | KeyProperties.PURPOSE_VERIFY);
+                // Authorized to be used with any digest (including no digest).
+                specBuilder.setDigests(
+                        KeyProperties.DIGEST_NONE,
+                        KeyProperties.DIGEST_MD5,
+                        KeyProperties.DIGEST_SHA1,
+                        KeyProperties.DIGEST_SHA224,
+                        KeyProperties.DIGEST_SHA256,
+                        KeyProperties.DIGEST_SHA384,
+                        KeyProperties.DIGEST_SHA512);
+                // Authorized to be used with any encryption and signature padding
+                // schemes (including no padding).
+                specBuilder.setEncryptionPaddings(
+                        KeyProperties.ENCRYPTION_PADDING_NONE,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+                        KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+                specBuilder.setSignaturePaddings(
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+                // Disable randomized encryption requirement to support encryption
+                // padding NONE above.
+                specBuilder.setRandomizedEncryptionRequired(false);
+                break;
+            default:
+                throw new ProviderException(
+                        "Unsupported algorithm: " + mKeymasterAlgorithm);
+        }
+
+        if (legacySpec.getKeySize() != -1) {
+            specBuilder.setKeySize(legacySpec.getKeySize());
+        }
+        if (legacySpec.getAlgorithmParameterSpec() != null) {
+            specBuilder.setAlgorithmParameterSpec(
+                    legacySpec.getAlgorithmParameterSpec());
+        }
+        specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+        specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+        specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+        specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+        specBuilder.setUserAuthenticationRequired(false);
+
+        return specBuilder.build();
+    }
+
     private void resetAll() {
         mEntryAlias = null;
         mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
@@ -464,7 +551,7 @@
         try {
             KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
 
-            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
+            KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor,
                     constructKeyGenerationArguments(), flags, additionalEntropy);
 
             AndroidKeyStorePublicKey publicKey =
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/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b22f358..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,9 +40,6 @@
 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;
@@ -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) {};
     }
 
@@ -111,23 +120,23 @@
     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
             SizeCompatUIController sizeCompatUI) {
-        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
+        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+                new StartingSurfaceDrawer(context, mainExecutor));
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUIController 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));
@@ -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/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/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 73371e7..56fe126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -75,7 +75,7 @@
      */
     public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
     public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
-    private static final int CHAIN_STIFFNESS = 600;
+    private static final int CHAIN_STIFFNESS = 800;
     public static final float DEFAULT_BOUNCINESS = 0.9f;
 
     private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
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/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 3064af6..843e27f 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,6 @@
 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;
@@ -585,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;
@@ -973,7 +978,8 @@
         finishResizeForMenu(destinationBounds);
     }
 
-    private void finishResizeForMenu(Rect destinationBounds) {
+    /** Moves the PiP menu to the destination bounds. */
+    public void finishResizeForMenu(Rect destinationBounds) {
         mPipMenuController.movePipMenu(null, null, destinationBounds);
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
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/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index c3970e3..4a2a032 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -552,6 +552,7 @@
         // mTouchHandler would rely on the bounds populated from mPipTaskOrganizer
         mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation, fromImeAdjustment,
                 fromShelfAdjustment, wct);
+        mPipTaskOrganizer.finishResizeForMenu(outBounds);
         mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBoundsState.getNormalBounds(),
                 outBounds, fromImeAdjustment, fromShelfAdjustment, rotation);
     }
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..eae8945 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
@@ -17,8 +17,7 @@
 package com.android.wm.shell.pip.phone;
 
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -325,7 +324,7 @@
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
-        mMenuController.hideMenuWithoutResize();
+        mMenuController.hideMenu(false /* animate */, false /* resize */);
         mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
     }
 
@@ -338,7 +337,7 @@
             Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
-        mMenuController.hideMenuWithoutResize();
+        mMenuController.hideMenu(true /* animate*/, false /* resize */);
         mPipTaskOrganizer.removePip();
     }
 
@@ -371,9 +370,9 @@
     /**
      * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
      */
-    void stashToEdge(float velocityX, @Nullable Runnable postBoundsUpdateCallback) {
-        mPipBoundsState.setStashed(velocityX < 0 ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT);
-        movetoTarget(velocityX, 0 /* velocityY */, postBoundsUpdateCallback, true /* isStash */);
+    void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+        velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+        movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
     }
 
     private void movetoTarget(
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 1ef9ffa..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,
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..afc7b52 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
@@ -19,7 +19,9 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
 import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -63,8 +65,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
@@ -809,7 +812,6 @@
             }
 
             if (touchState.startedDragging()) {
-                mPipBoundsState.setStashed(STASH_TYPE_NONE);
                 mSavedSnapFraction = -1f;
                 mPipDismissTargetHandler.showDismissTargetMaybe();
             }
@@ -862,10 +864,10 @@
 
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
-                if (mEnableStash && !mPipBoundsState.isStashed()
-                        && shouldStash(vel, getPossiblyMotionBounds())) {
-                    mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
+                if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+                    mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
                 } else {
+                    mPipBoundsState.setStashed(STASH_TYPE_NONE);
                     mMotionHelper.flingToSnapTarget(vel.x, vel.y,
                             this::flingEndAction /* endAction */);
                 }
@@ -876,6 +878,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);
@@ -916,6 +921,11 @@
                     && mPipExclusionBoundsChangeListener.get() != null) {
                 mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds());
             }
+            if (mPipBoundsState.getBounds().left < 0) {
+                mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+            } else {
+                mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+            }
         }
 
         private void flingEndAction() {
@@ -933,12 +943,12 @@
 
         private boolean shouldStash(PointF vel, Rect motionBounds) {
             // If user flings the PIP window above the minimum velocity, stash PIP.
-            // Only allow stashing to the edge if the user starts dragging the PIP from the
-            // opposite edge.
+            // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+            // edge.
             final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
-                    && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
                     || (vel.x > mStashVelocityThreshold
-                    && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT));
 
             // If User releases the PIP window while it's out of the display bounds, put
             // PIP into stashed mode.
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/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 48ee86c..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,134 +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
     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;
 
-    @VisibleForTesting
     public 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);
     }
 
-    public 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);
         }
     }
 
@@ -167,4 +199,14 @@
         }
         return context;
     }
+
+    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/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/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 2c6c7b3..d80699d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 909476
 # includes OWNERS from parent directories
 natanieljr@google.com
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 176b33d..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;
 
@@ -52,6 +53,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,6 +79,8 @@
     private Context mContext;
     @Mock
     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/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 0eb64e5..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.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.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.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/hwui/Android.bp b/libs/hwui/Android.bp
index 42c6d7c..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -357,12 +357,13 @@
         "libharfbuzz_ng",
         "liblog",
         "libminikin",
-        "libnativehelper",
         "libz",
         "libziparchive",
         "libjpeg",
     ],
 
+    static_libs: ["libnativehelper_lazy"],
+
     target: {
         android: {
             srcs: [ // sources that depend on android only libraries
@@ -444,6 +445,7 @@
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
         "hwui/Bitmap.cpp",
+        "hwui/BlurDrawLooper.cpp",
         "hwui/Canvas.cpp",
         "hwui/ImageDecoder.cpp",
         "hwui/MinikinSkia.cpp",
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/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8fddf71..1fddac4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -463,9 +463,7 @@
 }
 
 void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPoint(x, y, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); });
 }
 
 void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) {
@@ -493,9 +491,7 @@
 
 void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawRegion(region, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); });
 }
 
 void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@@ -509,24 +505,18 @@
 
 void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
                                 const Paint& paint) {
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawDRRect(outer, inner, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); });
 }
 
 void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) {
     if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawCircle(x, y, radius, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); });
 }
 
 void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawOval(oval, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); });
 }
 
 void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -547,9 +537,7 @@
     if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
         return;
     }
-    apply_looper(&paint, [&](const SkPaint& p) {
-        mCanvas->drawPath(path, p);
-    });
+    apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); });
 }
 
 void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 155f6df..eac3f22 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -208,29 +208,21 @@
      */
     PaintCoW&& filterPaint(PaintCoW&& paint) const;
 
+    // proc(const SkPaint& modifiedPaint)
     template <typename Proc> void apply_looper(const Paint* paint, Proc proc) {
         SkPaint skp;
-        SkDrawLooper* looper = nullptr;
+        BlurDrawLooper* looper = nullptr;
         if (paint) {
             skp = *filterPaint(paint);
             looper = paint->getLooper();
         }
         if (looper) {
-            SkSTArenaAlloc<256> alloc;
-            SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-            if (ctx) {
-                SkDrawLooper::Context::Info info;
-                for (;;) {
-                    SkPaint p = skp;
-                    if (!ctx->next(&info, &p)) {
-                        break;
-                    }
-                    mCanvas->save();
-                    mCanvas->translate(info.fTranslate.fX, info.fTranslate.fY);
-                    proc(p);
-                    mCanvas->restore();
-                }
-            }
+            looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+                mCanvas->save();
+                mCanvas->translate(offset.fX, offset.fY);
+                proc(modifiedPaint);
+                mCanvas->restore();
+            });
         } else {
             proc(skp);
         }
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
new file mode 100644
index 0000000..27a038d
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.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.
+ */
+
+#include "BlurDrawLooper.h"
+#include <SkMaskFilter.h>
+
+namespace android {
+
+BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset)
+        : mColor(color), mBlurSigma(blurSigma), mOffset(offset) {}
+
+BlurDrawLooper::~BlurDrawLooper() = default;
+
+SkPoint BlurDrawLooper::apply(SkPaint* paint) const {
+    paint->setColor(mColor);
+    if (mBlurSigma > 0) {
+        paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true));
+    }
+    return mOffset;
+}
+
+sk_sp<BlurDrawLooper> BlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, float blurSigma,
+                                           SkPoint offset) {
+    if (cs) {
+        SkPaint tmp;
+        tmp.setColor(color, cs);  // converts color to sRGB
+        color = tmp.getColor4f();
+    }
+    return sk_sp<BlurDrawLooper>(new BlurDrawLooper(color, blurSigma, offset));
+}
+
+}  // namespace android
diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h
new file mode 100644
index 0000000..7e6786f
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.h
@@ -0,0 +1,53 @@
+/*
+ * 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 ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+
+class SkColorSpace;
+
+namespace android {
+
+class BlurDrawLooper : public SkRefCnt {
+public:
+    static sk_sp<BlurDrawLooper> Make(SkColor4f, SkColorSpace*, float blurSigma, SkPoint offset);
+
+    ~BlurDrawLooper() override;
+
+    // proc(SkPoint offset, const SkPaint& modifiedPaint)
+    template <typename DrawProc>
+    void apply(const SkPaint& paint, DrawProc proc) const {
+        SkPaint p(paint);
+        proc(this->apply(&p), p);  // draw the shadow
+        proc({0, 0}, paint);       // draw the original (on top)
+    }
+
+private:
+    const SkColor4f mColor;
+    const float mBlurSigma;
+    const SkPoint mOffset;
+
+    SkPoint apply(SkPaint* paint) const;
+
+    BlurDrawLooper(SkColor4f, float, SkPoint);
+};
+
+}  // namespace android
+
+#endif  // ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 05bae5c..d9c9eee 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,11 +17,11 @@
 #ifndef ANDROID_GRAPHICS_PAINT_H_
 #define ANDROID_GRAPHICS_PAINT_H_
 
+#include "BlurDrawLooper.h"
 #include "Typeface.h"
 
 #include <cutils/compiler.h>
 
-#include <SkDrawLooper.h>
 #include <SkFont.h>
 #include <SkPaint.h>
 #include <string>
@@ -59,8 +59,8 @@
     SkFont& getSkFont() { return mFont; }
     const SkFont& getSkFont() const { return mFont; }
 
-    SkDrawLooper* getLooper() const { return mLooper.get(); }
-    void setLooper(sk_sp<SkDrawLooper> looper) { mLooper = std::move(looper); }
+    BlurDrawLooper* getLooper() const { return mLooper.get(); }
+    void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); }
 
     // These shadow the methods on SkPaint, but we need to so we can keep related
     // attributes in-sync.
@@ -155,7 +155,7 @@
 
 private:
     SkFont mFont;
-    sk_sp<SkDrawLooper> mLooper;
+    sk_sp<BlurDrawLooper> mLooper;
 
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 3c86b28..bcec0fa 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -25,7 +25,6 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
-#include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkFont.h"
 #include "SkFontMetrics.h"
@@ -39,6 +38,7 @@
 #include "unicode/ushape.h"
 #include "utils/Blur.h"
 
+#include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
 #include <hwui/Paint.h>
@@ -964,13 +964,13 @@
         }
         else {
             SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
-            paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+            paint->setLooper(BlurDrawLooper::Make(color, cs.get(), sigma, {dx, dy}));
         }
     }
 
     static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
+        return paint->getLooper() != nullptr;
     }
 
     static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) {
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/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index ee7c4d8..b288402 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,28 +194,20 @@
     return filterPaint(std::move(paint));
 }
 
-static SkDrawLooper* get_looper(const Paint* paint) {
+static BlurDrawLooper* get_looper(const Paint* paint) {
     return paint ? paint->getLooper() : nullptr;
 }
 
 template <typename Proc>
-void applyLooper(SkDrawLooper* looper, const SkPaint* paint, Proc proc) {
+void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) {
     if (looper) {
-        SkSTArenaAlloc<256> alloc;
-        SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
-        if (ctx) {
-            SkDrawLooper::Context::Info info;
-            for (;;) {
-                SkPaint p;
-                if (paint) {
-                    p = *paint;
-                }
-                if (!ctx->next(&info, &p)) {
-                    break;
-                }
-                proc(info.fTranslate.fX, info.fTranslate.fY, &p);
-            }
+        SkPaint p;
+        if (paint) {
+            p = *paint;
         }
+        looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+            proc(offset.fX, offset.fY, &modifiedPaint);
+        });
     } else {
         proc(0, 0, paint);
     }
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7951537..a1ba70a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -16,7 +16,6 @@
 
 #include "tests/common/TestUtils.h"
 
-#include <SkBlurDrawLooper.h>
 #include <SkColorMatrixFilter.h>
 #include <SkColorSpace.h>
 #include <SkImagePriv.h>
@@ -85,15 +84,3 @@
     ASSERT_EQ(sRGB1.get(), sRGB2.get());
 }
 
-TEST(SkiaBehavior, blurDrawLooper) {
-    sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
-
-    SkDrawLooper::BlurShadowRec blur;
-    bool success = looper->asABlurShadow(&blur);
-    ASSERT_TRUE(success);
-
-    ASSERT_EQ(SK_ColorRED, blur.fColor);
-    ASSERT_EQ(5.0f, blur.fSigma);
-    ASSERT_EQ(3.0f, blur.fOffset.fX);
-    ASSERT_EQ(4.0f, blur.fOffset.fY);
-}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index f77ca2a..dae3c94 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,7 +17,6 @@
 #include "tests/common/TestUtils.h"
 
 #include <hwui/Paint.h>
-#include <SkBlurDrawLooper.h>
 #include <SkCanvasStateUtils.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
@@ -37,7 +36,7 @@
     // it is transparent to ensure that we still draw the rect since it has a looper
     paint.setColor(SK_ColorTRANSPARENT);
     // this is how view's shadow layers are implemented
-    paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+    paint.setLooper(BlurDrawLooper::Make({0, 0, 0, 240.0f / 255}, nullptr, 6.0f, {0, 10}));
     canvas.drawRect(3, 3, 7, 7, paint);
 
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index e2fdf2f..09c6a4f 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -20,7 +20,6 @@
 #include <utils/Blur.h>
 
 #include <SkColorFilter.h>
-#include <SkDrawLooper.h>
 #include <SkPaint.h>
 #include <SkShader.h>
 
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/OWNERS b/media/OWNERS
index e741490..abfc8bf 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -26,3 +26,7 @@
 
 # SEO
 sungsoo@google.com
+
+# SEA/KIR/BVE
+jtinker@google.com
+robertshih@google.com
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8d090f8..d896c1f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -567,6 +567,25 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int FLAG_FROM_KEY = 1 << 12;
 
+    /** @hide */
+    @IntDef(flag = false, prefix = "FLAG", value = {
+            FLAG_SHOW_UI,
+            FLAG_ALLOW_RINGER_MODES,
+            FLAG_PLAY_SOUND,
+            FLAG_REMOVE_SOUND_AND_VIBRATE,
+            FLAG_VIBRATE,
+            FLAG_FIXED_VOLUME,
+            FLAG_BLUETOOTH_ABS_VOLUME,
+            FLAG_SHOW_SILENT_HINT,
+            FLAG_HDMI_SYSTEM_AUDIO_VOLUME,
+            FLAG_ACTIVE_MEDIA_ONLY,
+            FLAG_SHOW_UI_WARNINGS,
+            FLAG_SHOW_VIBRATE_HINT,
+            FLAG_FROM_KEY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
     // The iterator of TreeMap#entrySet() returns the entries in ascending key order.
     private static final TreeMap<Integer, String> FLAG_NAMES = new TreeMap<>();
 
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/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/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -2493,4 +2494,80 @@
             return mPlaybackId;
         }
     }
+
+    /**
+     * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
+     * instance.
+     */
+    @NonNull
+    public native List<LogMessage> getLogMessages();
+
+    /**
+     * A {@link LogMessage} records an event in the {@link MediaDrm} framework
+     * or vendor plugin.
+     */
+    public static class LogMessage {
+
+        /**
+         * Timing of the recorded event measured in milliseconds since the Epoch,
+         * 1970-01-01 00:00:00 +0000 (UTC).
+         */
+        public final long timestampMillis;
+
+        /**
+         * Priority of the recorded event.
+         * <p>
+         * Possible priority constants are defined in {@link Log}, e.g.:
+         * <ul>
+         *     <li>{@link Log#ASSERT}</li>
+         *     <li>{@link Log#ERROR}</li>
+         *     <li>{@link Log#WARN}</li>
+         *     <li>{@link Log#INFO}</li>
+         *     <li>{@link Log#DEBUG}</li>
+         *     <li>{@link Log#VERBOSE}</li>
+         * </ul>
+         */
+        @Log.Level
+        public final int priority;
+
+        /**
+         * Description of the recorded event.
+         */
+        @NonNull
+        public final String message;
+
+        private LogMessage(long timestampMillis, int priority, String message) {
+            this.timestampMillis = timestampMillis;
+            if (priority < Log.VERBOSE || priority > Log.ASSERT) {
+                throw new IllegalArgumentException("invalid log priority " + priority);
+            }
+            this.priority = priority;
+            this.message = message;
+        }
+
+        private char logPriorityChar() {
+            switch (priority) {
+                case Log.VERBOSE:
+                    return 'V';
+                case Log.DEBUG:
+                    return 'D';
+                case Log.INFO:
+                    return 'I';
+                case Log.WARN:
+                    return 'W';
+                case Log.ERROR:
+                    return 'E';
+                case Log.ASSERT:
+                    return 'F';
+                default:
+            }
+            return 'U';
+        }
+
+        @Override
+        public String toString() {
+            return String.format("LogMessage{%s %c %s}",
+                    Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ca0d29f..c51c9dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -921,7 +921,7 @@
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
+            mp.native_setAudioSessionId(audioSessionId);
             mp.setDataSource(context, uri);
             if (holder != null) {
                 mp.setDisplay(holder);
@@ -987,7 +987,7 @@
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.setAudioSessionId(audioSessionId);
+            mp.native_setAudioSessionId(audioSessionId);
 
             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
             afd.close();
@@ -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/metrics/Event.java b/media/java/android/media/metrics/Event.java
new file mode 100644
index 0000000..5646dcd
--- /dev/null
+++ b/media/java/android/media/metrics/Event.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 android.media.metrics;
+
+import android.annotation.IntRange;
+
+/**
+ * Abstract class for metrics events.
+ */
+public abstract class Event {
+    private final long mTimeSinceCreatedMillis;
+
+    // hide default constructor
+    /* package */ Event() {
+        mTimeSinceCreatedMillis = MediaMetricsManager.INVALID_TIMESTAMP;
+    }
+
+    protected Event(long timeSinceCreatedMillis) {
+        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+    }
+
+    /**
+     * Gets time since the corresponding instance is created in millisecond.
+     * @return the timestamp since the instance is created, or -1 if unknown.
+     */
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
+    }
+}
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index f2eae5f..de780f6 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -26,7 +26,8 @@
  */
 @SystemService(Context.MEDIA_METRICS_SERVICE)
 public class MediaMetricsManager {
-    // TODO: unhide APIs.
+    public static final long INVALID_TIMESTAMP = -1;
+
     private static final String TAG = "MediaMetricsManager";
 
     private IMediaMetricsManager mService;
diff --git a/media/java/android/media/metrics/NetworkEvent.java b/media/java/android/media/metrics/NetworkEvent.java
index a330bc0..029edeb 100644
--- a/media/java/android/media/metrics/NetworkEvent.java
+++ b/media/java/android/media/metrics/NetworkEvent.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -27,22 +28,30 @@
 import java.util.Objects;
 
 /**
- * Playback network event.
- * @hide
+ * Media network event.
  */
-public final class NetworkEvent implements Parcelable {
+public final class NetworkEvent extends Event implements Parcelable {
+    /** Network type is not specified. Default type. */
     public static final int NETWORK_TYPE_NONE = 0;
+    /** Other network type */
     public static final int NETWORK_TYPE_OTHER = 1;
+    /** Wi-Fi network */
     public static final int NETWORK_TYPE_WIFI = 2;
+    /** Ethernet network */
     public static final int NETWORK_TYPE_ETHERNET = 3;
+    /** 2G network */
     public static final int NETWORK_TYPE_2G = 4;
+    /** 3G network */
     public static final int NETWORK_TYPE_3G = 5;
+    /** 4G network */
     public static final int NETWORK_TYPE_4G = 6;
+    /** 5G NSA network */
     public static final int NETWORK_TYPE_5G_NSA = 7;
+    /** 5G SA network */
     public static final int NETWORK_TYPE_5G_SA = 8;
 
-    private final int mType;
-    private final long mTimeSincePlaybackCreatedMillis;
+    private final int mNetworkType;
+    private final long mTimeSinceCreatedMillis;
 
     /** @hide */
     @IntDef(prefix = "NETWORK_TYPE_", value = {
@@ -61,6 +70,7 @@
 
     /**
      * Network type to string.
+     * @hide
      */
     public static String networkTypeToString(@NetworkType int value) {
         switch (value) {
@@ -92,25 +102,34 @@
      *
      * @hide
      */
-    public NetworkEvent(@NetworkType int type, long timeSincePlaybackCreatedMillis) {
-        this.mType = type;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+    public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis) {
+        this.mNetworkType = type;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
+    /**
+     * Gets network type.
+     */
     @NetworkType
-    public int getType() {
-        return mType;
+    public int getNetworkType() {
+        return mNetworkType;
     }
 
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    /**
+     * Gets timestamp since the creation in milliseconds.
+     * @return the timestamp since the creation in milliseconds, or -1 if unknown.
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @Override
     public String toString() {
         return "NetworkEvent { "
-                + "type = " + mType + ", "
-                + "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis
+                + "networkType = " + mNetworkType + ", "
+                + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis
                 + " }";
     }
 
@@ -119,19 +138,19 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         NetworkEvent that = (NetworkEvent) o;
-        return mType == that.mType
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis;
+        return mNetworkType == that.mNetworkType
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mTimeSincePlaybackCreatedMillis);
+        return Objects.hash(mNetworkType, mTimeSinceCreatedMillis);
     }
 
     @Override
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeInt(mType);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeInt(mNetworkType);
+        dest.writeLong(mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -142,12 +161,15 @@
     /** @hide */
     /* package-private */ NetworkEvent(@NonNull android.os.Parcel in) {
         int type = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
 
-        this.mType = type;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mNetworkType = type;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
+    /**
+     * Used to read a NetworkEvent from a Parcel.
+     */
     public static final @NonNull Parcelable.Creator<NetworkEvent> CREATOR =
             new Parcelable.Creator<NetworkEvent>() {
         @Override
@@ -165,13 +187,11 @@
      * A builder for {@link NetworkEvent}
      */
     public static final class Builder {
-        private int mType;
-        private long mTimeSincePlaybackCreatedMillis;
+        private int mNetworkType = NETWORK_TYPE_NONE;
+        private long mTimeSinceCreatedMillis = -1;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder() {
         }
@@ -179,24 +199,24 @@
         /**
          * Sets network type.
          */
-        public @NonNull Builder setType(@NetworkType int value) {
-            mType = value;
+        public @NonNull Builder setNetworkType(@NetworkType int value) {
+            mNetworkType = value;
             return this;
         }
 
         /**
          * Sets timestamp since the creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
          */
-        public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) {
-            mTimeSincePlaybackCreatedMillis = value;
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
+            mTimeSinceCreatedMillis = value;
             return this;
         }
 
         /** Builds the instance. */
         public @NonNull NetworkEvent build() {
-            NetworkEvent o = new NetworkEvent(
-                    mType,
-                    mTimeSincePlaybackCreatedMillis);
+            NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis);
             return o;
         }
     }
diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java
index 94e55b4..1cadf3b 100644
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ b/media/java/android/media/metrics/PlaybackComponent.java
@@ -17,13 +17,10 @@
 package android.media.metrics;
 
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 
 /**
  * Interface for playback related components used by playback metrics.
- * @hide
  */
-@TestApi
 public interface PlaybackComponent {
 
     /**
diff --git a/media/java/android/media/metrics/PlaybackErrorEvent.java b/media/java/android/media/metrics/PlaybackErrorEvent.java
index db70005..5a0820d1 100644
--- a/media/java/android/media/metrics/PlaybackErrorEvent.java
+++ b/media/java/android/media/metrics/PlaybackErrorEvent.java
@@ -17,8 +17,10 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,17 +29,19 @@
 
 /**
  * Playback error event.
- * @hide
  */
-public final class PlaybackErrorEvent implements Parcelable {
+public final class PlaybackErrorEvent extends Event implements Parcelable {
+    /** Unknown error code. */
     public static final int ERROR_CODE_UNKNOWN = 0;
+    /** Error code for other errors */
     public static final int ERROR_CODE_OTHER = 1;
+    /** Error code for runtime errors */
     public static final int ERROR_CODE_RUNTIME = 2;
 
     private final @Nullable String mExceptionStack;
     private final int mErrorCode;
     private final int mSubErrorCode;
-    private final long mTimeSincePlaybackCreatedMillis;
+    private final long mTimeSinceCreatedMillis;
 
 
     /** @hide */
@@ -59,11 +63,11 @@
             @Nullable String exceptionStack,
             int errorCode,
             int subErrorCode,
-            long timeSincePlaybackCreatedMillis) {
+            long timeSinceCreatedMillis) {
         this.mExceptionStack = exceptionStack;
         this.mErrorCode = errorCode;
         this.mSubErrorCode = subErrorCode;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
     /** @hide */
@@ -72,17 +76,32 @@
         return mExceptionStack;
     }
 
+
+    /**
+     * Gets error code.
+     */
     @ErrorCode
     public int getErrorCode() {
         return mErrorCode;
     }
 
+
+    /**
+     * Gets sub error code.
+     */
+    @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE)
     public int getSubErrorCode() {
         return mSubErrorCode;
     }
 
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    /**
+     * Gets the timestamp since creation in milliseconds.
+     * @return the timestamp since the playback is created, or -1 if unknown.
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @Override
@@ -91,7 +110,7 @@
                 + "exceptionStack = " + mExceptionStack + ", "
                 + "errorCode = " + mErrorCode + ", "
                 + "subErrorCode = " + mSubErrorCode + ", "
-                + "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis
+                + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis
                 + " }";
     }
 
@@ -103,13 +122,13 @@
         return Objects.equals(mExceptionStack, that.mExceptionStack)
                 && mErrorCode == that.mErrorCode
                 && mSubErrorCode == that.mSubErrorCode
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis;
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mExceptionStack, mErrorCode, mSubErrorCode,
-                mTimeSincePlaybackCreatedMillis);
+            mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -120,7 +139,7 @@
         if (mExceptionStack != null) dest.writeString(mExceptionStack);
         dest.writeInt(mErrorCode);
         dest.writeInt(mSubErrorCode);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeLong(mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -134,14 +153,15 @@
         String exceptionStack = (flg & 0x1) == 0 ? null : in.readString();
         int errorCode = in.readInt();
         int subErrorCode = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
 
         this.mExceptionStack = exceptionStack;
         this.mErrorCode = errorCode;
         this.mSubErrorCode = subErrorCode;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
+
     public static final @NonNull Parcelable.Creator<PlaybackErrorEvent> CREATOR =
             new Parcelable.Creator<PlaybackErrorEvent>() {
         @Override
@@ -162,27 +182,18 @@
         private @Nullable Exception mException;
         private int mErrorCode;
         private int mSubErrorCode;
-        private long mTimeSincePlaybackCreatedMillis;
+        private long mTimeSinceCreatedMillis = -1;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
-        public Builder(
-                @Nullable Exception exception,
-                int errorCode,
-                int subErrorCode,
-                long timeSincePlaybackCreatedMillis) {
-            mException = exception;
-            mErrorCode = errorCode;
-            mSubErrorCode = subErrorCode;
-            mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        public Builder() {
         }
 
         /**
          * Sets the {@link Exception} object.
          */
+        @SuppressLint("MissingGetterMatchingBuilder") // Exception is not parcelable.
         public @NonNull Builder setException(@NonNull Exception value) {
             mException = value;
             return this;
@@ -199,16 +210,19 @@
         /**
          * Sets sub error code.
          */
-        public @NonNull Builder setSubErrorCode(int value) {
+        public @NonNull Builder setSubErrorCode(
+                @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int value) {
             mSubErrorCode = value;
             return this;
         }
 
         /**
-         * Set the timestamp in milliseconds.
+         * Set the timestamp since creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
          */
-        public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) {
-            mTimeSincePlaybackCreatedMillis = value;
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
+            mTimeSinceCreatedMillis = value;
             return this;
         }
 
@@ -227,7 +241,7 @@
                     stack,
                     mErrorCode,
                     mSubErrorCode,
-                    mTimeSincePlaybackCreatedMillis);
+                    mTimeSinceCreatedMillis);
             return o;
         }
     }
diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java
index 070b4e4..4aa61662 100644
--- a/media/java/android/media/metrics/PlaybackMetrics.java
+++ b/media/java/android/media/metrics/PlaybackMetrics.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -33,35 +34,57 @@
 
 /**
  * This class is used to store playback data.
- * @hide
  */
 public final class PlaybackMetrics implements Parcelable {
-    // TODO(b/177209128): JavaDoc for the constants.
+    /** Unknown stream source. */
     public static final int STREAM_SOURCE_UNKNOWN = 0;
+    /** Stream from network. */
     public static final int STREAM_SOURCE_NETWORK = 1;
+    /** Stream from device. */
     public static final int STREAM_SOURCE_DEVICE = 2;
+    /** Stream from more than one sources. */
     public static final int STREAM_SOURCE_MIXED = 3;
 
+    /** Unknown stream type. */
     public static final int STREAM_TYPE_UNKNOWN = 0;
+    /** Other stream type. */
     public static final int STREAM_TYPE_OTHER = 1;
+    /** Progressive stream type. */
     public static final int STREAM_TYPE_PROGRESSIVE = 2;
+    /** DASH (Dynamic Adaptive Streaming over HTTP) stream type. */
     public static final int STREAM_TYPE_DASH = 3;
+    /** HLS (HTTP Live Streaming) stream type. */
     public static final int STREAM_TYPE_HLS = 4;
+    /** SS (HTTP Smooth Streaming) stream type. */
     public static final int STREAM_TYPE_SS = 5;
 
+    /** VOD (Video on Demand) playback type. */
     public static final int PLAYBACK_TYPE_VOD = 0;
+    /** Live playback type. */
     public static final int PLAYBACK_TYPE_LIVE = 1;
+    /** Other playback type. */
     public static final int PLAYBACK_TYPE_OTHER = 2;
 
+    /** DRM is not used. */
     public static final int DRM_TYPE_NONE = 0;
+    /** Other DRM type. */
     public static final int DRM_TYPE_OTHER = 1;
+    /** Play ready DRM type. */
     public static final int DRM_TYPE_PLAY_READY = 2;
+    /** Widevine L1 DRM type. */
     public static final int DRM_TYPE_WIDEVINE_L1 = 3;
+    /** Widevine L3 DRM type. */
     public static final int DRM_TYPE_WIDEVINE_L3 = 4;
-    // TODO: add DRM_TYPE_CLEARKEY
+    /** Widevine L3 fallback DRM type. */
+    public static final int DRM_TYPE_WV_L3_FALLBACK = 5;
+    /** Clear key DRM type. */
+    public static final int DRM_TYPE_CLEARKEY = 6;
 
+    /** Main contents. */
     public static final int CONTENT_TYPE_MAIN = 0;
+    /** Advertisement contents. */
     public static final int CONTENT_TYPE_AD = 1;
+    /** Other contents. */
     public static final int CONTENT_TYPE_OTHER = 2;
 
 
@@ -102,7 +125,9 @@
         DRM_TYPE_OTHER,
         DRM_TYPE_PLAY_READY,
         DRM_TYPE_WIDEVINE_L1,
-        DRM_TYPE_WIDEVINE_L3
+        DRM_TYPE_WIDEVINE_L3,
+        DRM_TYPE_WV_L3_FALLBACK,
+        DRM_TYPE_CLEARKEY
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DrmType {}
@@ -173,6 +198,11 @@
         this.mNetworkTransferDurationMillis = networkTransferDurationMillis;
     }
 
+    /**
+     * Gets the media duration in milliseconds.
+     * @return the media duration in milliseconds, or -1 if unknown.
+     */
+    @IntRange(from = -1)
     public long getMediaDurationMillis() {
         return mMediaDurationMillis;
     }
@@ -241,28 +271,36 @@
 
     /**
      * Gets video frames played.
+     * @return the video frames played, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getVideoFramesPlayed() {
         return mVideoFramesPlayed;
     }
 
     /**
      * Gets video frames dropped.
+     * @return the video frames dropped, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getVideoFramesDropped() {
         return mVideoFramesDropped;
     }
 
     /**
      * Gets audio underrun count.
+     * @return the audio underrun count, or -1 if unknown.
      */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getAudioUnderrunCount() {
         return mAudioUnderrunCount;
     }
 
     /**
      * Gets number of network bytes read.
+     * @return the number of network bytes read, or -1 if unknown.
      */
+    @IntRange(from = -1)
     public long getNetworkBytesRead() {
         return mNetworkBytesRead;
     }
@@ -270,6 +308,7 @@
     /**
      * Gets number of local bytes read.
      */
+    @IntRange(from = -1)
     public long getLocalBytesRead() {
         return mLocalBytesRead;
     }
@@ -277,6 +316,7 @@
     /**
      * Gets network transfer duration in milliseconds.
      */
+    @IntRange(from = -1)
     public long getNetworkTransferDurationMillis() {
         return mNetworkTransferDurationMillis;
     }
@@ -415,34 +455,33 @@
      */
     public static final class Builder {
 
-        private long mMediaDurationMillis;
-        private int mStreamSource;
-        private int mStreamType;
-        private int mPlaybackType;
-        private int mDrmType;
-        private int mContentType;
+        private long mMediaDurationMillis = -1;
+        private int mStreamSource = STREAM_SOURCE_UNKNOWN;
+        private int mStreamType = STREAM_TYPE_UNKNOWN;
+        private int mPlaybackType = PLAYBACK_TYPE_OTHER;
+        private int mDrmType = DRM_TYPE_NONE;
+        private int mContentType = CONTENT_TYPE_OTHER;
         private @Nullable String mPlayerName;
         private @Nullable String mPlayerVersion;
         private @NonNull List<Long> mExperimentIds = new ArrayList<>();
-        private int mVideoFramesPlayed;
-        private int mVideoFramesDropped;
-        private int mAudioUnderrunCount;
-        private long mNetworkBytesRead;
-        private long mLocalBytesRead;
-        private long mNetworkTransferDurationMillis;
+        private int mVideoFramesPlayed = -1;
+        private int mVideoFramesDropped = -1;
+        private int mAudioUnderrunCount = -1;
+        private long mNetworkBytesRead = -1;
+        private long mLocalBytesRead = -1;
+        private long mNetworkTransferDurationMillis = -1;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder() {
         }
 
         /**
          * Sets the media duration in milliseconds.
+         * @param value the media duration in milliseconds. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setMediaDurationMillis(long value) {
+        public @NonNull Builder setMediaDurationMillis(@IntRange(from = -1) long value) {
             mMediaDurationMillis = value;
             return this;
         }
@@ -474,7 +513,7 @@
         /**
          * Sets the DRM type.
          */
-        public @NonNull Builder setDrmType(@StreamType int value) {
+        public @NonNull Builder setDrmType(@DrmType int value) {
             mDrmType = value;
             return this;
         }
@@ -513,48 +552,58 @@
 
         /**
          * Sets the video frames played.
+         * @param value the video frames played. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setVideoFramesPlayed(int value) {
+        public @NonNull Builder setVideoFramesPlayed(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mVideoFramesPlayed = value;
             return this;
         }
 
         /**
          * Sets the video frames dropped.
+         * @param value the video frames dropped. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setVideoFramesDropped(int value) {
+        public @NonNull Builder setVideoFramesDropped(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mVideoFramesDropped = value;
             return this;
         }
 
         /**
          * Sets the audio underrun count.
+         * @param value the audio underrun count. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setAudioUnderrunCount(int value) {
+        public @NonNull Builder setAudioUnderrunCount(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             mAudioUnderrunCount = value;
             return this;
         }
 
         /**
          * Sets the number of network bytes read.
+         * @param value the number of network bytes read. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setNetworkBytesRead(long value) {
+        public @NonNull Builder setNetworkBytesRead(@IntRange(from = -1) long value) {
             mNetworkBytesRead = value;
             return this;
         }
 
         /**
          * Sets the number of local bytes read.
+         * @param value the number of local bytes read. -1 indicates the value is unknown.
          */
-        public @NonNull Builder setLocalBytesRead(long value) {
+        public @NonNull Builder setLocalBytesRead(@IntRange(from = -1) long value) {
             mLocalBytesRead = value;
             return this;
         }
 
         /**
          * Sets the network transfer duration in milliseconds.
+         * @param value the network transfer duration in milliseconds.
+         *              -1 indicates the value is unknown.
          */
-        public @NonNull Builder setNetworkTransferDurationMillis(long value) {
+        public @NonNull Builder setNetworkTransferDurationMillis(@IntRange(from = -1) long value) {
             mNetworkTransferDurationMillis = value;
             return this;
         }
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index 3056e98..4ee8a45 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -45,7 +45,6 @@
 
     /**
      * Reports playback metrics.
-     * @hide
      */
     public void reportPlaybackMetrics(@NonNull PlaybackMetrics metrics) {
         mManager.reportPlaybackMetrics(mId, metrics);
@@ -53,33 +52,29 @@
 
     /**
      * Reports error event.
-     * @hide
      */
-    public void reportPlaybackErrorEvent(PlaybackErrorEvent event) {
+    public void reportPlaybackErrorEvent(@NonNull PlaybackErrorEvent event) {
         mManager.reportPlaybackErrorEvent(mId, event);
     }
 
     /**
      * Reports network event.
-     * @hide
      */
-    public void reportNetworkEvent(NetworkEvent event) {
+    public void reportNetworkEvent(@NonNull NetworkEvent event) {
         mManager.reportNetworkEvent(mId, event);
     }
 
     /**
      * Reports playback state event.
-     * @hide
      */
-    public void reportPlaybackStateEvent(PlaybackStateEvent event) {
+    public void reportPlaybackStateEvent(@NonNull PlaybackStateEvent event) {
         mManager.reportPlaybackStateEvent(mId, event);
     }
 
     /**
      * Reports track change event.
-     * @hide
      */
-    public void reportTrackChangeEvent(TrackChangeEvent event) {
+    public void reportTrackChangeEvent(@NonNull TrackChangeEvent event) {
         mManager.reportTrackChangeEvent(mId, event);
     }
 
diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java
index 6ce5bf0..8ca5b75 100644
--- a/media/java/android/media/metrics/PlaybackStateEvent.java
+++ b/media/java/android/media/metrics/PlaybackStateEvent.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -27,10 +28,8 @@
 
 /**
  * Playback state event.
- * @hide
  */
-public final class PlaybackStateEvent implements Parcelable {
-    // TODO: more states
+public final class PlaybackStateEvent extends Event implements Parcelable {
     /** Playback has not started (initial state) */
     public static final int STATE_NOT_STARTED = 0;
     /** Playback is buffering in the background for initial playback start */
@@ -41,23 +40,57 @@
     public static final int STATE_PLAYING = 3;
     /** Playback is paused but ready to play */
     public static final int STATE_PAUSED = 4;
+    /** Playback is handling a seek. */
+    public static final int STATE_SEEKING = 5;
+    /** Playback is buffering to resume active playback. */
+    public static final int STATE_BUFFERING = 6;
+    /** Playback is buffering while paused. */
+    public static final int STATE_PAUSED_BUFFERING = 7;
+    /** Playback is suppressed (e.g. due to audio focus loss). */
+    public static final int STATE_SUPPRESSED = 9;
+    /**
+     * Playback is suppressed (e.g. due to audio focus loss) while buffering to resume a playback.
+     */
+    public static final int STATE_SUPPRESSED_BUFFERING = 10;
+    /** Playback has reached the end of the media. */
+    public static final int STATE_ENDED = 11;
+    /** Playback is stopped and can be restarted. */
+    public static final int STATE_STOPPED = 12;
+    /** Playback is stopped due a fatal error and can be retried. */
+    public static final int STATE_FAILED = 13;
+    /** Playback is interrupted by an ad. */
+    public static final int STATE_INTERRUPTED_BY_AD = 14;
+    /** Playback is abandoned before reaching the end of the media. */
+    public static final int STATE_ABANDONED = 15;
 
-    private int mState;
-    private long mTimeSincePlaybackCreatedMillis;
+    private final int mState;
+    private final long mTimeSinceCreatedMillis;
 
     // These track ExoPlayer states. See the ExoPlayer documentation for the state transitions.
+    /** @hide */
     @IntDef(prefix = "STATE_", value = {
         STATE_NOT_STARTED,
         STATE_JOINING_BACKGROUND,
         STATE_JOINING_FOREGROUND,
         STATE_PLAYING,
-        STATE_PAUSED
+        STATE_PAUSED,
+        STATE_SEEKING,
+        STATE_BUFFERING,
+        STATE_PAUSED_BUFFERING,
+        STATE_SUPPRESSED,
+        STATE_SUPPRESSED_BUFFERING,
+        STATE_ENDED,
+        STATE_STOPPED,
+        STATE_FAILED,
+        STATE_INTERRUPTED_BY_AD,
+        STATE_ABANDONED,
     })
     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
     public @interface State {}
 
     /**
      * Converts playback state to string.
+     * @hide
      */
     public static String stateToString(@State int value) {
         switch (value) {
@@ -71,6 +104,26 @@
                 return "STATE_PLAYING";
             case STATE_PAUSED:
                 return "STATE_PAUSED";
+            case STATE_SEEKING:
+                return "STATE_SEEKING";
+            case STATE_BUFFERING:
+                return "STATE_BUFFERING";
+            case STATE_PAUSED_BUFFERING:
+                return "STATE_PAUSED_BUFFERING";
+            case STATE_SUPPRESSED:
+                return "STATE_SUPPRESSED";
+            case STATE_SUPPRESSED_BUFFERING:
+                return "STATE_SUPPRESSED_BUFFERING";
+            case STATE_ENDED:
+                return "STATE_ENDED";
+            case STATE_STOPPED:
+                return "STATE_STOPPED";
+            case STATE_FAILED:
+                return "STATE_FAILED";
+            case STATE_INTERRUPTED_BY_AD:
+                return "STATE_INTERRUPTED_BY_AD";
+            case STATE_ABANDONED:
+                return "STATE_ABANDONED";
             default:
                 return Integer.toHexString(value);
         }
@@ -83,14 +136,13 @@
      */
     public PlaybackStateEvent(
             int state,
-            long timeSincePlaybackCreatedMillis) {
+            long timeSinceCreatedMillis) {
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
         this.mState = state;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
     }
 
     /**
      * Gets playback state.
-     * @return
      */
     public int getState() {
         return mState;
@@ -98,9 +150,12 @@
 
     /**
      * Gets time since the corresponding playback is created in millisecond.
+     * @return the timestamp since the playback is created, or -1 if unknown.
      */
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @Override
@@ -109,18 +164,18 @@
         if (o == null || getClass() != o.getClass()) return false;
         PlaybackStateEvent that = (PlaybackStateEvent) o;
         return mState == that.mState
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis;
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mState, mTimeSincePlaybackCreatedMillis);
+        return Objects.hash(mState, mTimeSinceCreatedMillis);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mState);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeLong(mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -131,10 +186,10 @@
     /** @hide */
     /* package-private */ PlaybackStateEvent(@NonNull Parcel in) {
         int state = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
 
         this.mState = state;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
     }
 
     public static final @NonNull Parcelable.Creator<PlaybackStateEvent> CREATOR =
@@ -150,4 +205,43 @@
         }
     };
 
+    /**
+     * A builder for {@link PlaybackStateEvent}
+     */
+    public static final class Builder {
+        private int mState = STATE_NOT_STARTED;
+        private long mTimeSinceCreatedMillis = -1;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets playback state.
+         */
+        public @NonNull Builder setState(@State int value) {
+            mState = value;
+            return this;
+        }
+
+        /**
+         * Sets timestamp since the creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
+            mTimeSinceCreatedMillis = value;
+            return this;
+        }
+
+        /** Builds the instance. */
+        public @NonNull PlaybackStateEvent build() {
+            PlaybackStateEvent o = new PlaybackStateEvent(
+                    mState,
+                    mTimeSinceCreatedMillis);
+            return o;
+        }
+    }
 }
diff --git a/media/java/android/media/metrics/TrackChangeEvent.java b/media/java/android/media/metrics/TrackChangeEvent.java
index fff0e36..ef25357 100644
--- a/media/java/android/media/metrics/TrackChangeEvent.java
+++ b/media/java/android/media/metrics/TrackChangeEvent.java
@@ -17,6 +17,7 @@
 package android.media.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -28,20 +29,29 @@
 
 /**
  * Playback track change event.
- * @hide
  */
-public final class TrackChangeEvent implements Parcelable {
+public final class TrackChangeEvent extends Event implements Parcelable {
+    /** The track is off. */
     public static final int TRACK_STATE_OFF = 0;
+    /** The track is on. */
     public static final int TRACK_STATE_ON = 1;
 
+    /** Unknown track change reason. */
     public static final int TRACK_CHANGE_REASON_UNKNOWN = 0;
+    /** Other track change reason. */
     public static final int TRACK_CHANGE_REASON_OTHER = 1;
+    /** Track change reason for initial state. */
     public static final int TRACK_CHANGE_REASON_INITIAL = 2;
+    /** Track change reason for manual changes. */
     public static final int TRACK_CHANGE_REASON_MANUAL = 3;
+    /** Track change reason for adaptive changes. */
     public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4;
 
+    /** Audio track. */
     public static final int TRACK_TYPE_AUDIO = 0;
+    /** Video track. */
     public static final int TRACK_TYPE_VIDEO = 1;
+    /** Text track. */
     public static final int TRACK_TYPE_TEXT = 2;
 
     private final int mState;
@@ -50,7 +60,7 @@
     private final @Nullable String mSampleMimeType;
     private final @Nullable String mCodecName;
     private final int mBitrate;
-    private final long mTimeSincePlaybackCreatedMillis;
+    private final long mTimeSinceCreatedMillis;
     private final int mType;
     private final @Nullable String mLanguage;
     private final @Nullable String mLanguageRegion;
@@ -96,7 +106,7 @@
             @Nullable String sampleMimeType,
             @Nullable String codecName,
             int bitrate,
-            long timeSincePlaybackCreatedMillis,
+            long timeSinceCreatedMillis,
             int type,
             @Nullable String language,
             @Nullable String languageRegion,
@@ -110,7 +120,7 @@
         this.mSampleMimeType = sampleMimeType;
         this.mCodecName = codecName;
         this.mBitrate = bitrate;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
         this.mType = type;
         this.mLanguage = language;
         this.mLanguageRegion = languageRegion;
@@ -120,34 +130,60 @@
         this.mHeight = height;
     }
 
+    /**
+     * Gets track state.
+     */
     @TrackState
     public int getTrackState() {
         return mState;
     }
 
+    /**
+     * Gets track change reason.
+     */
     @TrackChangeReason
     public int getTrackChangeReason() {
         return mReason;
     }
 
+    /**
+     * Gets container MIME type.
+     */
     public @Nullable String getContainerMimeType() {
         return mContainerMimeType;
     }
 
+    /**
+     * Gets the MIME type of the video/audio/text samples.
+     */
     public @Nullable String getSampleMimeType() {
         return mSampleMimeType;
     }
 
+    /**
+     * Gets codec name.
+     */
     public @Nullable String getCodecName() {
         return mCodecName;
     }
 
+    /**
+     * Gets bitrate.
+     * @return the bitrate, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getBitrate() {
         return mBitrate;
     }
 
-    public long getTimeSincePlaybackCreatedMillis() {
-        return mTimeSincePlaybackCreatedMillis;
+    /**
+     * Gets timestamp since the creation in milliseconds.
+     * @return the timestamp since the creation in milliseconds, or -1 if unknown.
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
     }
 
     @TrackType
@@ -155,26 +191,55 @@
         return mType;
     }
 
+    /**
+     * Gets language code.
+     * @return a two-letter ISO 639-1 language code.
+     */
     public @Nullable String getLanguage() {
         return mLanguage;
     }
 
+
+    /**
+     * Gets language region code.
+     * @return an IETF BCP 47 optional language region subtag based on a two-letter country code.
+     */
     public @Nullable String getLanguageRegion() {
         return mLanguageRegion;
     }
 
+    /**
+     * Gets channel count.
+     * @return the channel count, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getChannelCount() {
         return mChannelCount;
     }
 
+    /**
+     * Gets sample rate.
+     * @return the sample rate, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getSampleRate() {
         return mSampleRate;
     }
 
+    /**
+     * Gets video width.
+     * @return the video width, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getWidth() {
         return mWidth;
     }
 
+    /**
+     * Gets video height.
+     * @return the video height, or -1 if unknown.
+     */
+    @IntRange(from = -1, to = Integer.MAX_VALUE)
     public int getHeight() {
         return mHeight;
     }
@@ -194,7 +259,7 @@
         if (mSampleMimeType != null) dest.writeString(mSampleMimeType);
         if (mCodecName != null) dest.writeString(mCodecName);
         dest.writeInt(mBitrate);
-        dest.writeLong(mTimeSincePlaybackCreatedMillis);
+        dest.writeLong(mTimeSinceCreatedMillis);
         dest.writeInt(mType);
         if (mLanguage != null) dest.writeString(mLanguage);
         if (mLanguageRegion != null) dest.writeString(mLanguageRegion);
@@ -218,7 +283,7 @@
         String sampleMimeType = (flg & 0x8) == 0 ? null : in.readString();
         String codecName = (flg & 0x10) == 0 ? null : in.readString();
         int bitrate = in.readInt();
-        long timeSincePlaybackCreatedMillis = in.readLong();
+        long timeSinceCreatedMillis = in.readLong();
         int type = in.readInt();
         String language = (flg & 0x100) == 0 ? null : in.readString();
         String languageRegion = (flg & 0x200) == 0 ? null : in.readString();
@@ -233,7 +298,7 @@
         this.mSampleMimeType = sampleMimeType;
         this.mCodecName = codecName;
         this.mBitrate = bitrate;
-        this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis;
+        this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
         this.mType = type;
         this.mLanguage = language;
         this.mLanguageRegion = languageRegion;
@@ -256,38 +321,24 @@
         }
     };
 
-
-
-    // Code below generated by codegen v1.0.22.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/metrics/TrackChangeEvent.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
     @Override
     public String toString() {
-        return "TrackChangeEvent { " +
-                "state = " + mState + ", " +
-                "reason = " + mReason + ", " +
-                "containerMimeType = " + mContainerMimeType + ", " +
-                "sampleMimeType = " + mSampleMimeType + ", " +
-                "codecName = " + mCodecName + ", " +
-                "bitrate = " + mBitrate + ", " +
-                "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis + ", " +
-                "type = " + mType + ", " +
-                "language = " + mLanguage + ", " +
-                "languageRegion = " + mLanguageRegion + ", " +
-                "channelCount = " + mChannelCount + ", " +
-                "sampleRate = " + mSampleRate + ", " +
-                "width = " + mWidth + ", " +
-                "height = " + mHeight +
-        " }";
+        return "TrackChangeEvent { "
+                + "state = " + mState + ", "
+                + "reason = " + mReason + ", "
+                + "containerMimeType = " + mContainerMimeType + ", "
+                + "sampleMimeType = " + mSampleMimeType + ", "
+                + "codecName = " + mCodecName + ", "
+                + "bitrate = " + mBitrate + ", "
+                + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis + ", "
+                + "type = " + mType + ", "
+                + "language = " + mLanguage + ", "
+                + "languageRegion = " + mLanguageRegion + ", "
+                + "channelCount = " + mChannelCount + ", "
+                + "sampleRate = " + mSampleRate + ", "
+                + "width = " + mWidth + ", "
+                + "height = " + mHeight
+                + " }";
     }
 
     @Override
@@ -301,7 +352,7 @@
                 && Objects.equals(mSampleMimeType, that.mSampleMimeType)
                 && Objects.equals(mCodecName, that.mCodecName)
                 && mBitrate == that.mBitrate
-                && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis
                 && mType == that.mType
                 && Objects.equals(mLanguage, that.mLanguage)
                 && Objects.equals(mLanguageRegion, that.mLanguageRegion)
@@ -314,7 +365,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mState, mReason, mContainerMimeType, mSampleMimeType, mCodecName,
-                mBitrate, mTimeSincePlaybackCreatedMillis, mType, mLanguage, mLanguageRegion,
+                mBitrate, mTimeSinceCreatedMillis, mType, mLanguage, mLanguageRegion,
                 mChannelCount, mSampleRate, mWidth, mHeight);
     }
 
@@ -323,32 +374,33 @@
      */
     public static final class Builder {
         // TODO: check track type for the setters.
-        private int mState;
-        private int mReason;
+        private int mState = TRACK_STATE_OFF;
+        private int mReason = TRACK_CHANGE_REASON_UNKNOWN;
         private @Nullable String mContainerMimeType;
         private @Nullable String mSampleMimeType;
         private @Nullable String mCodecName;
-        private int mBitrate;
-        private long mTimeSincePlaybackCreatedMillis;
-        private int mType;
+        private int mBitrate = -1;
+        private long mTimeSinceCreatedMillis = -1;
+        private final int mType;
         private @Nullable String mLanguage;
         private @Nullable String mLanguageRegion;
-        private int mChannelCount;
-        private int mSampleRate;
-        private int mWidth;
-        private int mHeight;
+        private int mChannelCount = -1;
+        private int mSampleRate = -1;
+        private int mWidth = -1;
+        private int mHeight = -1;
 
         private long mBuilderFieldsSet = 0L;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder(int type) {
             mType = type;
         }
 
+        /**
+         * Sets track state.
+         */
         public @NonNull Builder setTrackState(@TrackState int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1;
@@ -356,6 +408,9 @@
             return this;
         }
 
+        /**
+         * Sets track change reason.
+         */
         public @NonNull Builder setTrackChangeReason(@TrackChangeReason int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
@@ -363,6 +418,9 @@
             return this;
         }
 
+        /**
+         * Sets container MIME type.
+         */
         public @NonNull Builder setContainerMimeType(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
@@ -370,6 +428,9 @@
             return this;
         }
 
+        /**
+         * Sets the MIME type of the video/audio/text samples.
+         */
         public @NonNull Builder setSampleMimeType(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x8;
@@ -377,6 +438,9 @@
             return this;
         }
 
+        /**
+         * Sets codec name.
+         */
         public @NonNull Builder setCodecName(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
@@ -384,27 +448,33 @@
             return this;
         }
 
-        public @NonNull Builder setBitrate(int value) {
+        /**
+         * Sets bitrate in bits per second.
+         * @param value the bitrate in bits per second. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setBitrate(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
             mBitrate = value;
             return this;
         }
 
-        public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) {
+        /**
+         * Sets timestamp since the creation in milliseconds.
+         * @param value the timestamp since the creation in milliseconds.
+         *              -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x40;
-            mTimeSincePlaybackCreatedMillis = value;
+            mTimeSinceCreatedMillis = value;
             return this;
         }
 
-        public @NonNull Builder setTrackType(@TrackType int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x80;
-            mType = value;
-            return this;
-        }
-
+        /**
+         * Sets language code.
+         * @param value a two-letter ISO 639-1 language code.
+         */
         public @NonNull Builder setLanguage(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x100;
@@ -412,6 +482,11 @@
             return this;
         }
 
+        /**
+         * Sets language region code.
+         * @param value an IETF BCP 47 optional language region subtag based on a two-letter country
+         *              code.
+         */
         public @NonNull Builder setLanguageRegion(@NonNull String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x200;
@@ -419,28 +494,46 @@
             return this;
         }
 
-        public @NonNull Builder setChannelCount(int value) {
+        /**
+         * Sets channel count.
+         * @param value the channel count. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setChannelCount(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x400;
             mChannelCount = value;
             return this;
         }
 
-        public @NonNull Builder setSampleRate(int value) {
+        /**
+         * Sets sample rate.
+         * @param value the sample rate. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setSampleRate(
+                @IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x800;
             mSampleRate = value;
             return this;
         }
 
-        public @NonNull Builder setWidth(int value) {
+        /**
+         * Sets video width.
+         * @param value the video width. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setWidth(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1000;
             mWidth = value;
             return this;
         }
 
-        public @NonNull Builder setHeight(int value) {
+        /**
+         * Sets video height.
+         * @param value the video height. -1 indicates the value is unknown.
+         */
+        public @NonNull Builder setHeight(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2000;
             mHeight = value;
@@ -459,7 +552,7 @@
                     mSampleMimeType,
                     mCodecName,
                     mBitrate,
-                    mTimeSincePlaybackCreatedMillis,
+                    mTimeSinceCreatedMillis,
                     mType,
                     mLanguage,
                     mLanguageRegion,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index f580ea5..13a3436 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -1127,9 +1127,10 @@
          * toast showing the volume should be shown.
          *
          * @param sessionToken the remote media session token
-         * @param flags extra information about how to handle the volume change
+         * @param flags flags containing extra action or information regarding the volume change
          */
-        void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags);
+        void onVolumeChanged(@NonNull MediaSession.Token sessionToken,
+                @AudioManager.Flags int flags);
 
         /**
          * Called when the default remote session is changed where the default remote session
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
+        "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
     ],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 0e8719e..22c3572 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -37,6 +37,7 @@
 #include <mediadrm/DrmUtils.h>
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
+#include <utils/Vector.h>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -187,6 +188,11 @@
     jclass classId;
 };
 
+struct LogMessageFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -208,6 +214,7 @@
     jmethodID createFromParcelId;
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
+    LogMessageFields logMessage;
 };
 
 static fields_t gFields;
@@ -224,6 +231,19 @@
     return result;
 }
 
+jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) {
+    jclass clazz = gFields.arraylistClassId;
+    jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+    clazz = gFields.logMessage.classId;
+    for (auto log: logs) {
+        jobject jLog = env->NewObject(clazz, gFields.logMessage.init,
+                static_cast<jlong>(log.timeMs),
+                static_cast<jint>(log.priority),
+                env->NewStringUTF(log.message.c_str()));
+        env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog);
+    }
+    return arrayList;
+}
 }  // namespace anonymous
 
 // ----------------------------------------------------------------------------
@@ -907,6 +927,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus");
     gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V");
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
+    gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+    GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -1996,6 +2020,22 @@
     throwExceptionAsNecessary(env, err, "Failed to set playbackId");
 }
 
+static jobject android_media_MediaDrm_getLogMessages(
+        JNIEnv *env, jobject thiz) {
+    sp<IDrm> drm = GetDrm(env, thiz);
+    if (!CheckDrm(env, drm)) {
+        return NULL;
+    }
+
+    Vector<drm::V1_4::LogMessage> logs;
+    status_t err = drm->getLogMessages(logs);
+    ALOGI("drm->getLogMessages %zu logs", logs.size());
+    if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) {
+        return NULL;
+    }
+    return hidlLogMessagesToJavaList(env, logs);
+}
+
 static const JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaDrm_native_release },
 
@@ -2123,6 +2163,9 @@
 
     { "setPlaybackId", "([BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_setPlaybackId },
+
+    { "getLogMessages", "()Ljava/util/List;",
+      (void *)android_media_MediaDrm_getLogMessages },
 };
 
 int register_android_media_Drm(JNIEnv *env) {
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 f618890..f31d465 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -262,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;
     }
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 0613223..9e36642 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -322,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;
     }
 
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/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 72589e3..c30d4bf 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -165,6 +165,7 @@
     protected void onStop() {
         super.onStop();
         if (!isFinishing() && !isChangingConfigurations()) {
+            Log.i(LOG_TAG, "onStop() - cancelling");
             cancel();
         }
     }
@@ -195,7 +196,6 @@
         titleView.setText(title);
     }
 
-    //TODO put in resources xmls
     private ProgressBar getProgressBar() {
         final ProgressBar progressBar = new ProgressBar(this);
         progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 606cd57..67d4b41 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -309,7 +309,7 @@
     }
 
     private void onDeviceLost(@Nullable DeviceFilterPair device) {
-        if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
+        Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
         Handler.getMain().sendMessage(obtainMessage(
                 DeviceDiscoveryService::onDeviceLostMainThread, this, device));
     }
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 8db8d76..73e1511 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -14,15 +14,37 @@
 // limitations under the License.
 //
 
-// TODO: use a java_library in the bootclasspath instead
 filegroup {
-    name: "framework-connectivity-sources",
+    name: "framework-connectivity-internal-sources",
     srcs: [
         "src/**/*.java",
         "src/**/*.aidl",
     ],
     path: "src",
     visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-aidl-export-sources",
+    srcs: [
+        "aidl-export/**/*.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+    name: "framework-connectivity-sources",
+    srcs: [
+        ":framework-connectivity-internal-sources",
+        ":framework-connectivity-aidl-export-sources",
+    ],
+    visibility: [
         "//frameworks/base",
         "//packages/modules/Connectivity:__subpackages__",
     ],
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl b/packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
diff --git a/packages/Connectivity/framework/src/android/net/DhcpInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/DhcpInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/IpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpConfiguration.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpPrefix.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl
diff --git a/packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl b/packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl
diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkAddress.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl
diff --git a/packages/Connectivity/framework/src/android/net/LinkProperties.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkProperties.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl
diff --git a/packages/Connectivity/framework/src/android/net/MacAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/MacAddress.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl
diff --git a/packages/Connectivity/framework/src/android/net/Network.aidl b/packages/Connectivity/framework/aidl-export/android/net/Network.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/Network.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/Network.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkRequest.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl
diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ProxyInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/RouteInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/RouteInfo.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl
diff --git a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl b/packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl
diff --git a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl
rename to packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 92d7bf0..d7c8291 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -456,7 +456,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_WIFI      = TetheringManager.TETHERING_WIFI;
+    public static final int TETHERING_WIFI      = 0;
 
     /**
      * USB tethering type.
@@ -464,7 +464,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_USB       = TetheringManager.TETHERING_USB;
+    public static final int TETHERING_USB       = 1;
 
     /**
      * Bluetooth tethering type.
@@ -472,7 +472,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
+    public static final int TETHERING_BLUETOOTH = 2;
 
     /**
      * Wifi P2p tethering type.
@@ -2799,7 +2799,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+    public static final int TETHER_ERROR_NO_ERROR = 0;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
      * {@hide}
@@ -2875,8 +2875,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_PROVISION_FAILED =
-            TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+    public static final int TETHER_ERROR_PROVISION_FAILED = 11;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
      * {@hide}
@@ -2890,8 +2889,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
-            TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 9d67f0b..26d14cb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2085,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;
     }
@@ -2330,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 b4a651c..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;
 
@@ -172,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.
          */
@@ -217,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);
         }
@@ -234,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;
         }
 
@@ -248,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;
         }
 
@@ -308,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;
         }
 
@@ -430,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
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
index 4e89414..a174a7b 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
@@ -41,7 +41,6 @@
 
     /**
      * Prefix for tap interfaces created by this class.
-     * @hide
      */
     public static final String TEST_TAP_PREFIX = "testtap";
 
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/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/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
index 81ca9ea..228de03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
@@ -163,8 +163,12 @@
         long locationAccessFinishTime = 0L;
         // Earliest time for a location access to end and still be shown in list.
         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
+        // Compute the most recent access time from all op entries.
         for (AppOpsManager.OpEntry entry : entries) {
-            locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
+            long lastAccessTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
+            if (lastAccessTime > locationAccessFinishTime) {
+                locationAccessFinishTime = lastAccessTime;
+            }
         }
         // Bail out if the entry is out of date.
         if (locationAccessFinishTime < recentLocationCutoffTime) {
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/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 35a4e81..a15ceb6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -100,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" />
@@ -344,6 +345,12 @@
     <!-- Permissions required for CTS test - AdbManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
 
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
+
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
+
     <!-- Permission needed for CTS test - DisplayTest -->
     <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
 
@@ -405,6 +412,9 @@
     <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
     <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
 
+    <!-- Permission required for CTS test - PeopleManagerTest -->
+    <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
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/README.md b/packages/SystemUI/README.md
index ee8d023..60994d8 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -144,6 +144,10 @@
 
 Delegates SysUI events to WM Shell controllers.
 
+### [com.android.systemui.people.widget.PeopleSpaceWidgetEnabler](/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java)
+
+Enables People Space widgets.
+
 ---
 
  * [Plugins](/packages/SystemUI/docs/plugins.md)
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/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
similarity index 90%
rename from packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
rename to packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index 1f6b24b..dd35dd9 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2017 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.
@@ -20,6 +20,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?android:attr/colorBackground"
         android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
 </vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
index b7a9fafd..604ab72 100644
--- a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
@@ -16,8 +16,23 @@
 * limitations under the License.
 */
 -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <solid android:color="?android:attr/colorBackground" />
-  <corners android:radius="10dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:id="@+id/background">
+    <shape>
+      <solid android:color="?android:attr/colorControlNormal" />
+      <corners android:radius="10dp" />
+    </shape>
+  </item>
+  <item android:id="@+id/ripple">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+      <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+          <solid android:color="?android:attr/colorControlNormal" />
+          <corners android:radius="10dp" />
+        </shape>
+    </item>
+    </ripple>
+  </item>
+</layer-list>
+
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
similarity index 64%
copy from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
copy to packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
index 59af775..51c442a 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 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.
@@ -11,9 +12,9 @@
   ~ 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.
+  ~ limitations under the License
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorBackground" />
-    <corners android:radius="@dimen/people_space_widget_round_radius" />
-</shape>
\ No newline at end of file
+
+<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/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index a928b75..2a5784a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,7 +40,7 @@
     <dimen name="keyguard_security_view_top_margin">8dp</dimen>
     <dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
 
-    <dimen name="keyguard_eca_top_margin">24dp</dimen>
+    <dimen name="keyguard_eca_top_margin">18dp</dimen>
 
     <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
          Should be 0 on devices with plenty of room (e.g. tablets) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index cd82b80..2391803 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -52,9 +52,8 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
     <style name="NumPadKey.Delete">
-        <item name="android:src">@drawable/ic_backspace_black_24dp</item>
-        <item name="android:tint">?android:attr/textColorSecondary</item>
-        <item name="android:tintMode">src_in</item>
+        <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="android:src">@drawable/ic_backspace_24dp</item>
     </style>
     <style name="NumPadKey.Enter">
       <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/circle_green_10dp.xml
similarity index 80%
copy from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
copy to packages/SystemUI/res/drawable/circle_green_10dp.xml
index 59af775..571ec62 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/circle_green_10dp.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -13,7 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorBackground" />
-    <corners android:radius="@dimen/people_space_widget_round_radius" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <size android:height="10dp"
+          android:width="10dp" />
+    <solid android:color="#34A853" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_content_background.xml b/packages/SystemUI/res/drawable/people_space_content_background.xml
index 53108409..32314d2 100644
--- a/packages/SystemUI/res/drawable/people_space_content_background.xml
+++ b/packages/SystemUI/res/drawable/people_space_content_background.xml
@@ -16,5 +16,5 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android" >
     <solid android:color="?android:attr/colorControlHighlight" />
-    <corners android:radius="@dimen/people_space_widget_radius" />
+    <corners android:radius="@dimen/people_space_image_radius" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
similarity index 87%
rename from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
rename to packages/SystemUI/res/drawable/people_space_new_story_outline.xml
index 59af775..a1737f9 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
@@ -13,7 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
     <solid android:color="?android:attr/colorBackground" />
-    <corners android:radius="@dimen/people_space_widget_round_radius" />
+    <stroke android:width="2dp" android:color="?android:attr/colorAccent" />
 </shape>
\ No newline at end of file
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/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
index e9f3424..b1c1328 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
@@ -19,70 +19,78 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
     <LinearLayout
-        android:background="@drawable/people_space_round_tile_view_card"
+        android:background="@drawable/people_space_tile_view_card"
         android:id="@+id/item"
-        android:paddingVertical="6dp"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
         <LinearLayout
             android:orientation="horizontal"
-            android:gravity="center_vertical"
-            android:paddingStart="12dp"
+            android:gravity="center"
+            android:paddingVertical="2dp"
+            android:paddingHorizontal="8dp"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
-
+            <LinearLayout
+                android:background="@drawable/people_space_new_story_outline"
+                android:id="@+id/person_icon_with_story"
+                android:gravity="center_horizontal"
+                android:layout_width="60dp"
+                android:layout_height="60dp">
                 <ImageView
-                    android:id="@+id/person_icon"
-                    android:layout_width="60dp"
-                    android:layout_height="60dp" />
+                    android:id="@+id/person_icon_inside_ring"
+                    android:layout_marginEnd="4dp"
+                    android:layout_marginStart="4dp"
+                    android:layout_marginBottom="4dp"
+                    android:layout_marginTop="4dp"
+                    android:layout_width="52dp"
+                    android:layout_height="52dp"/>
+            </LinearLayout>
+            <ImageView
+                android:id="@+id/person_icon_only"
+                android:layout_width="60dp"
+                android:layout_height="60dp"/>
 
-                <LinearLayout
-                    android:background="@drawable/people_space_rounded_border"
-                    android:layout_marginStart="-12dp"
-                    android:layout_marginTop="28dp"
-                    android:layout_marginBottom="14dp"
-                    android:layout_width="16dp"
-                    android:layout_height="16dp">
-
-                    <ImageView
-                        android:id="@+id/package_icon"
-                        android:layout_width="12dp"
-                        android:layout_marginStart="2dp"
-                        android:layout_marginEnd="2dp"
-                        android:layout_marginBottom="2dp"
-                        android:layout_marginTop="2dp"
-                        android:layout_height="12dp" />
-                </LinearLayout>
+            <ImageView
+                android:id="@+id/package_icon"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginStart="-20dp"
+                android:layout_marginTop="22dp"/>
 
             <LinearLayout
                 android:orientation="vertical"
                 android:paddingStart="8dp"
-                android:paddingEnd="12dp"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
 
+                <ImageView
+                    android:id="@+id/availability"
+                    android:layout_width="10dp"
+                    android:layout_height="10dp"
+                    android:background="@drawable/circle_green_10dp"/>
                 <TextView
                     android:id="@+id/name"
+                    android:text="@string/empty_user_name"
                     android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
                     android:textColor="?android:attr/textColorPrimary"
-                    android:textSize="16sp"
+                    android:textSize="14sp"
                     android:maxLines="1"
                     android:ellipsize="end"
                     android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
+                    android:layout_height="wrap_content"/>
 
                 <TextView
-                    android:id="@+id/status"
+                    android:id="@+id/last_interaction"
+                    android:text="@string/empty_status"
                     android:textColor="?android:attr/textColorSecondary"
                     android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:paddingVertical="3dp"
                     android:textSize="12sp"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:maxLines="3"
-                    android:ellipsize="end" />
+                    android:ellipsize="end"/>
             </LinearLayout>
         </LinearLayout>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
new file mode 100644
index 0000000..9ea7aa3
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
@@ -0,0 +1,154 @@
+<?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="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <RelativeLayout
+        android:background="@drawable/people_space_tile_view_card"
+        android:id="@+id/item"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <include layout="@layout/punctuation_layout"/>
+        <RelativeLayout
+            android:gravity="start"
+            android:id="@+id/column_one"
+            android:paddingVertical="10dp"
+            android:paddingStart="8dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent">
+            <TextView
+                android:id="@+id/subtext"
+                android:layout_toStartOf="@+id/content_layout"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:gravity="top|start"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textSize="12sp"
+                android:maxWidth="60dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:ellipsize="end"/>
+            <LinearLayout
+                android:orientation="horizontal"
+                android:id="@+id/avatar_and_app_icon"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentStart="true"
+                android:gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:id="@+id/person_icon_with_story"
+                    android:background="@drawable/people_space_new_story_outline"
+                    android:gravity="center_horizontal"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp">
+                    <ImageView
+                        android:id="@+id/person_icon_inside_ring"
+                        android:layout_marginEnd="4dp"
+                        android:layout_marginStart="4dp"
+                        android:layout_marginBottom="4dp"
+                        android:layout_marginTop="4dp"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"/>
+                </LinearLayout>
+                <ImageView
+                    android:id="@+id/person_icon_only"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"/>
+                <ImageView
+                    android:id="@id/package_icon"
+                    android:layout_marginStart="-16dp"
+                    android:layout_marginTop="18dp"
+                    android:paddingBottom="10dp"
+                    android:paddingEnd="8dp"
+                    android:layout_width="28dp"
+                    android:layout_height="32dp"/>
+            </LinearLayout>
+        </RelativeLayout>
+        <LinearLayout
+            android:id="@+id/content_layout"
+            android:paddingBottom="4dp"
+            android:paddingStart="4dp"
+            android:paddingTop="8dp"
+            android:paddingEnd="8dp"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@id/column_one"
+            android:gravity="top|end"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/content"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:gravity="top|end"
+                android:textSize="12sp"
+                android:paddingTop="2dp"
+                android:paddingEnd="4dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:ellipsize="end"/>
+            <LinearLayout
+                android:id="@+id/content_background"
+                android:background="@drawable/people_space_content_background"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <ImageView
+                    android:id="@+id/image"
+                    android:adjustViewBounds="true"
+                    android:maxHeight="44dp"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:scaleType="centerCrop"/>
+            </LinearLayout>
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/person_label"
+            android:paddingBottom="10dp"
+            android:paddingEnd="8dp"
+            android:gravity="start|bottom"
+            android:layout_toEndOf="@id/column_one"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/content_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <ImageView
+                android:id="@+id/availability"
+                android:layout_width="10dp"
+                android:layout_height="10dp"
+                android:paddingVertical="2dp"
+                android:background="@drawable/circle_green_10dp"/>
+            <TextView
+                android:id="@+id/name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        </LinearLayout>
+    </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
index 03589d3..3300495 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -14,183 +14,144 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     <RelativeLayout
         android:background="@drawable/people_space_tile_view_card"
+        android:id="@+id/item"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
-        <LinearLayout
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="start">
-            <TextView
-                android:id="@+id/punctuation1"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="350" />
-            <TextView
-                android:id="@+id/punctuation2"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="5" />
-            <TextView
-                android:id="@+id/punctuation3"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="355"/>
-            <TextView
-                android:id="@+id/punctuation4"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="-5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="10" />
-            <TextView
-                android:id="@+id/punctuation5"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="15" />
-            <TextView
-                android:id="@+id/punctuation6"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textSize="36sp"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="-5dp"
-                android:layout_marginStart="25dp"
-                android:maxLines="1"
-                android:alpha="0.2"
-                android:rotation="345" />
-        </LinearLayout>
-        <LinearLayout
-            android:id="@+id/item"
-            android:orientation="vertical"
-            android:paddingTop="6dp"
-            android:layout_width="match_parent"
+        <RelativeLayout
+            android:gravity="start"
+            android:id="@+id/column_one"
+            android:paddingVertical="8dp"
+            android:paddingStart="8dp"
+            android:layout_width="wrap_content"
             android:layout_height="match_parent">
             <LinearLayout
                 android:orientation="horizontal"
-                android:paddingHorizontal="12dp"
-                android:paddingBottom="4dp"
-                android:gravity="top"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-
-                <ImageView
-                    android:id="@+id/person_icon"
-                    android:layout_width="34dp"
-                    android:layout_height="34dp" />
-
-                <LinearLayout
-                    android:background="@drawable/people_space_rounded_border"
-                    android:layout_marginStart="-5dp"
-                    android:layout_marginTop="18dp"
-                    android:layout_width="8dp"
-                    android:layout_height="8dp">
-
-                    <ImageView
-                        android:id="@+id/package_icon"
-                        android:layout_width="6dp"
-                        android:layout_marginEnd="1dp"
-                        android:layout_marginStart="1dp"
-                        android:layout_marginBottom="1dp"
-                        android:layout_marginTop="1dp"
-                        android:layout_height="6dp" />
-                </LinearLayout>
-
-                <LinearLayout
-                    android:orientation="vertical"
-                    android:paddingStart="6dp"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <TextView
-                        android:id="@+id/name"
-                        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                        android:textColor="?android:attr/textColorPrimary"
-                        android:textSize="14sp"
-                        android:maxLines="1"
-                        android:ellipsize="end"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content" />
-
-                    <TextView
-                        android:id="@+id/time"
-                        android:textColor="?android:attr/textColorSecondary"
-                        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                        android:textSize="10sp"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:maxLines="1"
-                        android:ellipsize="end" />
-                </LinearLayout>
-            </LinearLayout>
-            <LinearLayout
                 android:id="@+id/content_background"
                 android:background="@drawable/people_space_content_background"
-                android:layout_gravity="center"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-                <TextView
-                    android:id="@+id/content"
-                    android:paddingVertical="3dp"
-                    android:paddingHorizontal="12dp"
-                    android:gravity="center"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textSize="16sp"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:maxLines="2"
-                    android:ellipsize="end" />
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_width="60dp"
+                android:layout_height="60dp">
                 <ImageView
                     android:id="@+id/image"
+                    android:gravity="center"
+                    android:background="@drawable/people_space_content_background"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:visibility="gone"
                     android:scaleType="centerCrop"/>
+                <ImageView
+                    android:id="@+id/status_defined_icon"
+                    android:gravity="start|top"
+                    android:layout_marginStart="-52dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_width="18dp"
+                    android:layout_height="18dp"/>
             </LinearLayout>
+            <LinearLayout
+                android:orientation="horizontal"
+                android:id="@+id/avatar_and_app_icon"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentStart="true"
+                android:paddingStart="4dp"
+                android:gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:id="@+id/person_icon_with_story"
+                    android:background="@drawable/people_space_new_story_outline"
+                    android:gravity="center_horizontal"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp">
+                    <ImageView
+                        android:id="@+id/person_icon_inside_ring"
+                        android:layout_marginEnd="4dp"
+                        android:layout_marginStart="4dp"
+                        android:layout_marginBottom="4dp"
+                        android:layout_marginTop="4dp"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"/>
+                </LinearLayout>
+                <ImageView
+                    android:id="@+id/person_icon_only"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"/>
+                <ImageView
+                    android:id="@id/package_icon"
+                    android:layout_marginStart="-16dp"
+                    android:layout_marginTop="18dp"
+                    android:paddingBottom="10dp"
+                    android:paddingEnd="8dp"
+                    android:layout_width="28dp"
+                    android:layout_height="32dp"/>
+            </LinearLayout>
+        </RelativeLayout>
+        <LinearLayout
+            android:id="@+id/content_layout"
+            android:paddingTop="10dp"
+            android:paddingBottom="4dp"
+            android:paddingHorizontal="8dp"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@id/column_one"
+            android:gravity="top|end"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/status"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="12sp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:ellipsize="end"/>
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/person_label"
+            android:paddingBottom="10dp"
+            android:paddingEnd="8dp"
+            android:gravity="start|bottom"
+            android:layout_toEndOf="@id/column_one"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/content_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/time"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textSize="12sp"
+                android:paddingVertical="2dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:visibility="gone"
+                android:ellipsize="end"/>
+            <ImageView
+                android:id="@+id/availability"
+                android:layout_width="10dp"
+                android:layout_height="10dp"
+                android:paddingVertical="2dp"
+                android:background="@drawable/circle_green_10dp"/>
+            <TextView
+                android:id="@+id/name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
         </LinearLayout>
     </RelativeLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/punctuation_layout.xml b/packages/SystemUI/res/layout/punctuation_layout.xml
new file mode 100644
index 0000000..25c7648
--- /dev/null
+++ b/packages/SystemUI/res/layout/punctuation_layout.xml
@@ -0,0 +1,100 @@
+<?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/punctuation_layout"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="start">
+    <TextView
+        android:id="@+id/punctuation1"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="350"/>
+    <TextView
+        android:id="@+id/punctuation2"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="5"/>
+    <TextView
+        android:id="@+id/punctuation3"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="355"/>
+    <TextView
+        android:id="@+id/punctuation4"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="10"/>
+    <TextView
+        android:id="@+id/punctuation5"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="15"/>
+    <TextView
+        android:id="@+id/punctuation6"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="36sp"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-5dp"
+        android:layout_marginStart="25dp"
+        android:maxLines="1"
+        android:alpha="0.2"
+        android:rotation="345"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index dc341274..059bda3 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -54,16 +54,4 @@
         android:paddingBottom="10dp"
         android:importantForAccessibility="yes" />
 
-    <TextView
-        android:id="@+id/header_debug_info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:fontFamily="sans-serif-condensed"
-        android:padding="2dp"
-        android:textColor="#00A040"
-        android:textSize="11dp"
-        android:textStyle="bold"
-        android:visibility="invisible"/>
-
 </com.android.systemui.qs.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 859d904..a8ef7c3 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -32,6 +32,12 @@
         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"
@@ -63,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"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6196225..13c0110 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -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>
 
@@ -307,6 +312,7 @@
         <item>com.android.systemui.accessibility.SystemActions</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.wmshell.WMShell</item>
+        <item>com.android.systemui.people.widget.PeopleSpaceWidgetEnabler</item>
     </string-array>
 
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 594fbdf..b07df9c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -802,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">32dp</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>
@@ -1348,8 +1348,8 @@
     <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
     <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
 
-    <dimen name="people_space_widget_radius">24dp</dimen>
-    <dimen name="people_space_widget_round_radius">100dp</dimen>
+    <dimen name="people_space_widget_radius">28dp</dimen>
+    <dimen name="people_space_image_radius">20dp</dimen>
     <dimen name="people_space_widget_background_padding">6dp</dimen>
 
     <dimen name="rounded_slider_height">48dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6d731f8..d4bb128 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,5 +38,8 @@
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
 
+    <!-- The new animations to/from lockscreen and AOD! -->
+    <bool name="flag_lockscreen_animations">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 d997ca2..3b42600 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -247,6 +247,10 @@
     <string name="screenshot_dismiss_description">Dismiss screenshot</string>
     <!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
     <string name="screenshot_preview_description">Screenshot preview</string>
+    <!-- Content description for the top boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+    <string name="screenshot_top_boundary">Top boundary</string>
+    <!-- Content description for the bottom boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+    <string name="screenshot_bottom_boundary">Bottom boundary</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
@@ -1341,6 +1345,9 @@
     <!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
 
+    <!-- Monitoring dialog: Description of Network Logging in the work profile. [CHAR LIMIT=NONE]-->
+    <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string>
+
     <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_named_vpn">You\'re connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
 
@@ -2810,8 +2817,26 @@
     <string name="less_than_timestamp" translatable="false">Less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string>
     <!-- Timestamp for notification when over a certain time window [CHAR LIMIT=120] -->
     <string name="over_timestamp" translatable="false">Over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string>
-    <!-- Status text for a birthday today [CHAR LIMIT=120] -->
-    <string name="birthday_status" translatable="false">Today is their birthday!</string>
+    <!-- Status text for a birthday today [CHAR LIMIT=30] -->
+    <string name="birthday_status" translatable="false">Birthday</string>
+    <!-- Status text for an upcoming birthday [CHAR LIMIT=30] -->
+    <string name="upcoming_birthday_status" translatable="false">Birthday soon</string>
+    <!-- Status text for an anniversary [CHAR LIMIT=30] -->
+    <string name="anniversary_status" translatable="false">Anniversary</string>
+    <!-- Status text for sharing location [CHAR LIMIT=30] -->
+    <string name="location_status" translatable="false">Sharing location</string>
+    <!-- Status text for a new story posted [CHAR LIMIT=30] -->
+    <string name="new_story_status" translatable="false">New story</string>
+    <!-- Status text for watching a video [CHAR LIMIT=30] -->
+    <string name="video_status" translatable="false">Watching</string>
+    <!-- Status text for listening to audio [CHAR LIMIT=30] -->
+    <string name="audio_status" translatable="false">Listening</string>
+    <!-- Status text for playing a game [CHAR LIMIT=30] -->
+    <string name="game_status" translatable="false">Playing</string>
+    <!-- Empty user name before user has selected a friend [CHAR LIMIT=30] -->
+    <string name="empty_user_name" translatable="false">Your friend</string>
+    <!-- Empty status shown before user has selected a friend [CHAR LIMIT=30] -->
+    <string name="empty_status" translatable="false">Their status</string>
 
     <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
     [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index d0c63a8..fbdac5e1 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -16,11 +16,11 @@
 
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
     android:minWidth="140dp"
-    android:minHeight="40dp"
+    android:minHeight="55dp"
     android:minResizeWidth="110dp"
-    android:minResizeHeight="40dp"
+    android:minResizeHeight="55dp"
     android:updatePeriodMillis="60000"
-    android:previewImage="@drawable/ic_android"
+    android:previewImage="@drawable/ic_person"
     android:resizeMode="horizontal|vertical"
     android:configure="com.android.systemui.people.PeopleSpaceActivity"
     android:initialLayout="@layout/people_space_widget">
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/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/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..4e06491 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -24,14 +24,12 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 
 import com.android.internal.widget.LockscreenCredential;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -167,6 +165,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() {
@@ -174,10 +186,6 @@
             key.reloadColors();
         }
         mPasswordEntry.reloadColors();
-        int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary)
-                .getDefaultColor();
-        mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor));
-
         mDeleteButton.reloadColors();
         mOkButton.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 6fb6760..bfe7f8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.View;
 
@@ -78,6 +79,8 @@
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+        mView.updateLogoutView(shouldShowLogout());
     }
 
     @Override
@@ -245,6 +248,11 @@
         }
     }
 
+    private boolean shouldShowLogout() {
+        return mKeyguardUpdateMonitor.isLogoutEnabled()
+                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+    }
+
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
@@ -271,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();
         }
@@ -302,7 +310,7 @@
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
                 refreshTime();
                 mView.updateOwnerInfo();
-                mView.updateLogoutView();
+                mView.updateLogoutView(shouldShowLogout());
             }
         }
 
@@ -320,12 +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());
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index cdf9858..97d6e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -17,14 +17,16 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.view.ContextThemeWrapper;
 import android.view.ViewGroup;
 
 import androidx.annotation.StyleRes;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -34,13 +36,18 @@
 class NumPadAnimator {
     private ValueAnimator mAnimator;
     private GradientDrawable mBackground;
+    private RippleDrawable mRipple;
+    private GradientDrawable mRippleMask;
     private int mMargin;
     private int mNormalColor;
     private int mHighlightColor;
     private int mStyle;
 
-    NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) {
-        mBackground = (GradientDrawable) background.mutate();
+    NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) {
+        LayerDrawable ld = (LayerDrawable) drawable.mutate();
+        mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background);
+        mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple);
+        mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask);
         mStyle = style;
 
         reloadColors(context);
@@ -49,13 +56,14 @@
 
         // Actual values will be updated later, usually during an onLayout() call
         mAnimator = ValueAnimator.ofFloat(0f);
-        mAnimator.setDuration(250);
-        mAnimator.setInterpolator(Interpolators.LINEAR);
+        mAnimator.setDuration(100);
+        mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(1);
         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator anim) {
                     mBackground.setCornerRadius((float) anim.getAnimatedValue());
-                    mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor,
-                            anim.getAnimatedFraction()));
+                    mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
                 }
         });
 
@@ -66,9 +74,9 @@
     }
 
     void onLayout(int height) {
-        float startRadius = height / 10f;
-        float endRadius = height / 2f;
-        mBackground.setCornerRadius(endRadius);
+        float startRadius = height / 2f;
+        float endRadius = height / 4f;
+        mBackground.setCornerRadius(startRadius);
         mAnimator.setFloatValues(startRadius, endRadius);
     }
 
@@ -91,6 +99,7 @@
         a.recycle();
 
         mBackground.setColor(mNormalColor);
+        mRipple.setColor(ColorStateList.valueOf(mHighlightColor));
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3728106..8cb1bc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -16,45 +16,60 @@
 package com.android.keyguard;
 
 import android.content.Context;
-import android.graphics.drawable.GradientDrawable;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.LayerDrawable;
+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);
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 attrs.getStyleAttribute());
     }
 
     @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..a4a781d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,10 +18,11 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 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
@@ -126,11 +127,22 @@
 
         setContentDescription(mDigitText.getText().toString());
 
-        mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
                 R.style.NumPadKey);
     }
 
     /**
+     * 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/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
new file mode 100644
index 0000000..c4be1ba535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.clock;
+
+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/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 78f7966..865ca40 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -19,18 +19,15 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
@@ -40,7 +37,6 @@
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 import com.android.systemui.util.NotificationChannels;
 
@@ -125,21 +121,6 @@
                             mServices[i].onBootCompleted();
                         }
                     }
-
-                    // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
-                    // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
-                    try {
-                        int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
-                                Settings.Global.SHOW_PEOPLE_SPACE, 1);
-                        context.getPackageManager().setComponentEnabledSetting(
-                                new ComponentName(context, PeopleSpaceWidgetProvider.class),
-                                showPeopleSpace == 1
-                                        ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                                PackageManager.DONT_KILL_APP);
-                    } catch (Exception e) {
-                        Log.w(TAG, "Error enabling People Space widget:", e);
-                    }
                 }
             }, bootCompletedFilter);
 
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/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/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index e5c9d10..ec3188a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.media.systemsounds.HomeSoundEffectController;
+import com.android.systemui.people.widget.PeopleSpaceWidgetEnabler;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
@@ -177,4 +178,10 @@
     @IntoMap
     @ClassKey(HomeSoundEffectController.class)
     public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
+
+    /** Inject into PeopleSpaceWidgetEnabler. */
+    @Binds
+    @IntoMap
+    @ClassKey(PeopleSpaceWidgetEnabler.class)
+    public abstract SystemUI bindPeopleSpaceWidgetEnabler(PeopleSpaceWidgetEnabler sysui);
 }
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/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/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 3a06f7a..2873cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.view.View;
 
 /**
@@ -29,7 +30,7 @@
  * See {@link com.android.systemui.statusbar.phone.KeyguardBottomAreaView}.
  */
 public class KeyguardIndication {
-    @NonNull
+    @Nullable
     private final CharSequence mMessage;
     @NonNull
     private final ColorStateList mTextColor;
@@ -56,7 +57,7 @@
     /**
      * Message to display
      */
-    public @NonNull CharSequence getMessage() {
+    public @Nullable CharSequence getMessage() {
         return mMessage;
     }
 
@@ -88,6 +89,17 @@
         return mBackground;
     }
 
+    @Override
+    public String toString() {
+        String str = "KeyguardIndication{";
+        if (!TextUtils.isEmpty(mMessage)) str += "mMessage=" + mMessage;
+        if (mIcon != null) str += " mIcon=" + mIcon;
+        if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener;
+        if (mBackground != null) str += " mBackground=" + mBackground;
+        str += "}";
+        return str;
+    }
+
     /**
      * KeyguardIndication Builder
      */
@@ -101,7 +113,7 @@
         public Builder() { }
 
         /**
-         * Required field. Message to display.
+         * Message to display. Indication requires a non-null message or icon.
          */
         public Builder setMessage(@NonNull CharSequence message) {
             this.mMessage = message;
@@ -117,9 +129,9 @@
         }
 
         /**
-         * Optional. Icon to show next to the text. Icon location changes based on language
-         * display direction. For LTR, icon shows to the left of the message. For RTL, icon shows
-         * to the right of the message.
+         * Icon to show next to the text. Indication requires a non-null icon or message.
+         * Icon location changes based on language display direction. For LTR, icon shows to the
+         * left of the message. For RTL, icon shows to the right of the message.
          */
         public Builder setIcon(Drawable icon) {
             this.mIcon = icon;
@@ -146,8 +158,13 @@
          * Build the KeyguardIndication.
          */
         public KeyguardIndication build() {
-            if (mMessage == null) throw new IllegalStateException("message must be set");
-            if (mTextColor == null) throw new IllegalStateException("text color must be set");
+            if (TextUtils.isEmpty(mMessage) && mIcon == null) {
+                throw new IllegalStateException("message or icon must be set");
+            }
+            if (mTextColor == null) {
+                throw new IllegalStateException("text color must be set");
+            }
+
             return new KeyguardIndication(
                     mMessage, mTextColor, mIcon, mOnClickListener, mBackground);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 8c04143..d4678f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.text.TextUtils;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -105,8 +104,7 @@
     public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
             boolean showImmediately) {
         final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
-        final boolean hasNewIndication = newIndication != null
-                && !TextUtils.isEmpty(newIndication.getMessage());
+        final boolean hasNewIndication = newIndication != null;
         if (!hasNewIndication) {
             mIndicationMessages.remove(type);
             mIndicationQueue.removeIf(x -> x == type);
@@ -203,7 +201,6 @@
             mIndicationQueue.add(type); // re-add to show later
         }
 
-        // pass the style update to be run right before our new indication is shown:
         mView.switchIndication(mIndicationMessages.get(type));
 
         // only schedule next indication if there's more than just this indication in the queue
@@ -289,8 +286,7 @@
         if (hasIndications()) {
             pw.println("    All messages:");
             for (int type : mIndicationMessages.keySet()) {
-                pw.println("        type=" + type
-                        + " message=" + mIndicationMessages.get(type).getMessage());
+                pw.println("        type=" + type + " " + mIndicationMessages.get(type));
             }
         }
     }
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 9e5b225..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,6 +29,8 @@
 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;
@@ -62,7 +64,11 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index dab4d0b..1660dea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -855,6 +855,14 @@
 
     @Override
     public void onRotationProposal(final int rotation, boolean isValid) {
+        if (mNavigationBarView == null) {
+            if (RotationContextButton.DEBUG_ROTATION) {
+                Log.v(TAG, "onRotationProposal proposedRotation=" +
+                        Surface.rotationToString(rotation) + ", mNavigationBarView is null");
+            }
+            return;
+        }
+
         final int winRotation = mNavigationBarView.getDisplay().getRotation();
         final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
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 c67aef6..2f9b17a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,6 +19,8 @@
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
 
+import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle;
+
 import android.app.Activity;
 import android.app.INotificationManager;
 import android.app.people.IPeopleManager;
@@ -39,6 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
+import java.util.Collections;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -49,6 +52,7 @@
 public class PeopleSpaceActivity extends Activity {
 
     private static final String TAG = "PeopleSpaceActivity";
+    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
     private ViewGroup mPeopleSpaceLayout;
     private IPeopleManager mPeopleManager;
@@ -134,9 +138,11 @@
     /** Stores the user selected configuration for {@code mAppWidgetId}. */
     private void storeWidgetConfiguration(PeopleSpaceTile tile) {
         if (PeopleSpaceUtils.DEBUG) {
-            Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
-                    + tile.getId() + " for widget ID: "
-                    + mAppWidgetId);
+            if (DEBUG) {
+                Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
+                        + tile.getId() + " for widget ID: "
+                        + mAppWidgetId);
+            }
         }
 
         PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId);
@@ -144,12 +150,22 @@
         // TODO: Populate new widget with existing conversation notification, if there is any.
         PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
                 mPeopleManager);
+        if (mLauncherApps != null) {
+            try {
+                if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+                mLauncherApps.cacheShortcuts(tile.getPackageName(),
+                        Collections.singletonList(tile.getId()),
+                        getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+            } catch (Exception e) {
+                Log.w(TAG, "Exception caching shortcut:" + e);
+            }
+        }
         finishActivity();
     }
 
     /** Finish activity with a successful widget configuration result. */
     private void finishActivity() {
-        if (PeopleSpaceUtils.DEBUG) Log.d(TAG, "Widget added!");
+        if (DEBUG) Log.d(TAG, "Widget added!");
         mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
         setActivityResult(RESULT_OK);
         finish();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index dd05484..cd1131b 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -17,12 +17,21 @@
 package com.android.systemui.people;
 
 import static android.app.Notification.EXTRA_MESSAGES;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_MEDIA;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
 
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -37,6 +46,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.icu.text.MeasureFormat;
 import android.icu.util.Measure;
 import android.icu.util.MeasureUnit;
@@ -49,6 +59,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;
@@ -70,6 +81,7 @@
 import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -286,7 +298,7 @@
         SharedPreferences.Editor widgetEditor = widgetSp.edit();
         widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName());
         widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId());
-        int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+        int userId = getUserId(tile);
         widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId);
         widgetEditor.apply();
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -438,23 +450,147 @@
     }
 
     /** 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;
+        RemoteViews viewsForTile = getViewForTile(context, tile);
+        RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile);
+        return setLaunchIntents(context, views, tile, appWidgetId);
+    }
+
+    /**
+     * The prioritization for the {@code tile} content is missed calls, followed by notification
+     * content, then birthdays, then the most recent status, and finally last interaction.
+     */
+    private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) {
         if (tile.getNotificationKey() != null) {
-            views = createNotificationRemoteViews(context, tile);
-        } else if (tile.getBirthdayText() != null) {
-            views = createStatusRemoteViews(context, tile);
-        } else {
-            views = createLastInteractionRemoteViews(context, tile);
+            if (DEBUG) Log.d(TAG, "Create notification view");
+            return createNotificationRemoteViews(context, tile);
         }
-        return setCommonRemoteViewsFields(context, views, tile, appWidgetId);
+
+        // TODO: Add sorting when we expose timestamp of statuses.
+        List<ConversationStatus> statusesForEntireView =
+                tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
+                        c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+        ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
+        if (birthdayStatus != null) {
+            if (DEBUG) Log.d(TAG, "Create birthday view");
+            return createStatusRemoteViews(context, birthdayStatus);
+        }
+
+        if (!statusesForEntireView.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Create status view for: " + statusesForEntireView.get(0).getActivity());
+            }
+            return createStatusRemoteViews(context, statusesForEntireView.get(0));
+        }
+
+        return createLastInteractionRemoteViews(context, tile);
+    }
+
+    @Nullable
+    private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
+            List<ConversationStatus> statuses) {
+        Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+                c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+        if (birthdayStatus.isPresent()) {
+            return birthdayStatus.get();
+        }
+        if (!TextUtils.isEmpty(tile.getBirthdayText())) {
+            return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns whether a {@code status} should have its own entire templated view.
+     *
+     * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+     * not valid to compose an entire view.
+     */
+    private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+        switch (status.getActivity()) {
+            // Birthday & Anniversary don't require text provided or icon provided.
+            case ACTIVITY_BIRTHDAY:
+            case ACTIVITY_ANNIVERSARY:
+                return true;
+            default:
+                // For future birthday, location, new story, video, music, game, and other, the
+                // app must provide either text or an icon.
+                return !TextUtils.isEmpty(status.getDescription())
+                        || status.getIcon() != null;
+        }
+    }
+
+    private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status) {
+        RemoteViews views = new RemoteViews(
+                context.getPackageName(), R.layout.people_space_small_avatar_tile);
+        CharSequence statusText = status.getDescription();
+        if (TextUtils.isEmpty(statusText)) {
+            statusText = getStatusTextByType(context, status.getActivity());
+        }
+        views.setTextViewText(R.id.status, statusText);
+        Icon statusIcon = status.getIcon();
+        if (statusIcon != null) {
+            views.setImageViewIcon(R.id.image, statusIcon);
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
+        } else {
+            views.setViewVisibility(R.id.content_background, View.GONE);
+        }
+        // TODO: Set status pre-defined icons
+        return views;
+    }
+
+    private static String getStatusTextByType(Context context, int activity) {
+        switch (activity) {
+            case ACTIVITY_BIRTHDAY:
+                return context.getString(R.string.birthday_status);
+            case ACTIVITY_UPCOMING_BIRTHDAY:
+                return context.getString(R.string.upcoming_birthday_status);
+            case ACTIVITY_ANNIVERSARY:
+                return context.getString(R.string.anniversary_status);
+            case ACTIVITY_LOCATION:
+                return context.getString(R.string.location_status);
+            case ACTIVITY_NEW_STORY:
+                return context.getString(R.string.new_story_status);
+            case ACTIVITY_MEDIA:
+                return context.getString(R.string.video_status);
+            case ACTIVITY_GAME:
+                return context.getString(R.string.game_status);
+            default:
+                return EMPTY_STRING;
+        }
     }
 
     private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
-            PeopleSpaceTile tile, int appWidgetId) {
+            PeopleSpaceTile tile) {
         try {
+            boolean isAvailable =
+                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+                            c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+            if (isAvailable) {
+                views.setViewVisibility(R.id.availability, View.VISIBLE);
+            } else {
+                views.setViewVisibility(R.id.availability, View.GONE);
+            }
+            boolean hasNewStory =
+                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+                            c -> c.getActivity() == ACTIVITY_NEW_STORY);
+            if (hasNewStory) {
+                views.setViewVisibility(R.id.person_icon_with_story, View.VISIBLE);
+                views.setViewVisibility(R.id.person_icon_only, View.GONE);
+                views.setImageViewIcon(R.id.person_icon_inside_ring, tile.getUserIcon());
+            } else {
+                views.setViewVisibility(R.id.person_icon_with_story, View.GONE);
+                views.setViewVisibility(R.id.person_icon_only, View.VISIBLE);
+                views.setImageViewIcon(R.id.person_icon_only, tile.getUserIcon());
+            }
+
             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(
@@ -462,9 +598,16 @@
                                     tile.getPackageName())
                     )
             );
-            views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
+            return views;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to set common fields: " + e);
+        }
+        return views;
+    }
 
+    private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
+            PeopleSpaceTile tile, int appWidgetId) {
+        try {
             Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
             activityIntent.addFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK
@@ -482,48 +625,42 @@
                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
             return views;
         } catch (Exception e) {
-            Log.e(TAG, "Failed to set common fields: " + e);
+            Log.e(TAG, "Failed to add launch intents: " + e);
         }
-        return null;
+        return views;
     }
 
     private static RemoteViews createNotificationRemoteViews(Context context,
             PeopleSpaceTile tile) {
         RemoteViews views = new RemoteViews(
-                context.getPackageName(), R.layout.people_space_small_avatar_tile);
+                context.getPackageName(), R.layout.people_space_notification_content_tile);
         Uri image = tile.getNotificationDataUri();
         if (image != null) {
-            //TODO: Use NotificationInlineImageCache
+            // TODO: Use NotificationInlineImageCache
             views.setImageViewUri(R.id.image, image);
-            views.setViewVisibility(R.id.image, View.VISIBLE);
+            views.setViewVisibility(R.id.content_background, View.VISIBLE);
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
             views.setViewVisibility(R.id.content, View.GONE);
         } else {
             CharSequence content = tile.getNotificationContent();
             views = setPunctuationRemoteViewsFields(views, content);
             views.setTextViewText(R.id.content, content);
             views.setViewVisibility(R.id.content, View.VISIBLE);
-            views.setViewVisibility(R.id.image, View.GONE);
+            views.setViewVisibility(R.id.content_background, View.GONE);
         }
-        views.setTextViewText(R.id.time, PeopleSpaceUtils.getLastInteractionString(
+        // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
+        views.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString(
                 context, tile.getLastInteractionTimestamp(), false));
         return views;
     }
 
-    private static RemoteViews createStatusRemoteViews(Context context,
-            PeopleSpaceTile tile) {
-        RemoteViews views = new RemoteViews(
-                context.getPackageName(), R.layout.people_space_large_avatar_tile);
-        views.setTextViewText(R.id.status, tile.getBirthdayText());
-        return views;
-    }
-
     private static RemoteViews createLastInteractionRemoteViews(Context context,
             PeopleSpaceTile tile) {
         RemoteViews views = new RemoteViews(
                 context.getPackageName(), R.layout.people_space_large_avatar_tile);
         String status = PeopleSpaceUtils.getLastInteractionString(
                 context, tile.getLastInteractionTimestamp(), true);
-        views.setTextViewText(R.id.status, status);
+        views.setTextViewText(R.id.last_interaction, status);
         return views;
     }
 
@@ -612,11 +749,27 @@
                 .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) {
         try {
-            int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+            int userId = getUserId(tile);
             String pkg = tile.getPackageName();
             return peopleManager.getLastInteraction(pkg, userId, tile.getId());
         } catch (Exception e) {
@@ -664,20 +817,35 @@
         Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction);
         MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
                 MeasureFormat.FormatWidth.WIDE);
+        MeasureFormat shortFormatter = MeasureFormat.getInstance(Locale.getDefault(),
+                MeasureFormat.FormatWidth.SHORT);
         if (durationSinceLastInteraction.toHours() < MIN_HOUR) {
-            return context.getString(includeLastChatted ? R.string.last_interaction_status_less_than
-                            : R.string.less_than_timestamp,
-                    formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status_less_than,
+                        formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE)));
         } else if (durationSinceLastInteraction.toDays() < ONE_DAY) {
-            return context.getString(
-                    includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
-                    formatter.formatMeasures(
-                            new Measure(durationSinceLastInteraction.toHours(), MeasureUnit.HOUR)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status,
+                        formatter.formatMeasures(
+                                new Measure(durationSinceLastInteraction.toHours(),
+                                        MeasureUnit.HOUR)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toHours(),
+                            MeasureUnit.HOUR)));
         } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
-            return context.getString(
-                    includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
-                    formatter.formatMeasures(
-                            new Measure(durationSinceLastInteraction.toDays(), MeasureUnit.DAY)));
+            if (includeLastChatted) {
+                return context.getString(R.string.last_interaction_status,
+                        formatter.formatMeasures(
+                                new Measure(durationSinceLastInteraction.toDays(),
+                                        MeasureUnit.DAY)));
+            }
+            return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toHours(),
+                            MeasureUnit.DAY)));
         } else {
             return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
                             ? (includeLastChatted ? R.string.last_interaction_status :
@@ -701,7 +869,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) {
@@ -792,8 +960,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);
     }
 
@@ -866,4 +1033,14 @@
     public static String getKey(String shortcutId, String packageName, int userId) {
         return shortcutId + "/" + userId + "/" + packageName;
     }
+
+    /** Returns the userId associated with a {@link PeopleSpaceTile} */
+    public static int getUserId(PeopleSpaceTile tile) {
+        return getUserHandle(tile).getIdentifier();
+    }
+
+    /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */
+    public static UserHandle getUserHandle(PeopleSpaceTile tile) {
+        return UserHandle.getUserHandleForUid(tile.getUid());
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java
new file mode 100644
index 0000000..b188acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.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 com.android.systemui.people.widget;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.FeatureFlags;
+
+import javax.inject.Inject;
+
+/**
+ * Enables People Space widgets.
+ */
+@SysUISingleton
+public class PeopleSpaceWidgetEnabler extends SystemUI {
+    private static final String TAG = "PeopleSpaceWdgtEnabler";
+    private Context mContext;
+    private FeatureFlags mFeatureFlags;
+
+    @Inject
+    public PeopleSpaceWidgetEnabler(Context context, FeatureFlags featureFlags) {
+        super(context);
+        mContext = context;
+        mFeatureFlags = featureFlags;
+    }
+
+    @Override
+    public void start() {
+        Log.d(TAG, "Starting service");
+        try {
+            boolean showPeopleSpace = mFeatureFlags.isPeopleTileEnabled();
+            mContext.getPackageManager().setComponentEnabledSetting(
+                    new ComponentName(mContext, PeopleSpaceWidgetProvider.class),
+                    showPeopleSpace
+                            ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                            : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+        } catch (Exception e) {
+            Log.w(TAG, "Error enabling People Space widget:", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index f5577d3..3d1055f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,13 +16,20 @@
 
 package com.android.systemui.people.widget;
 
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
 import android.app.PendingIntent;
 import android.app.people.IPeopleManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProvider;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 import android.widget.RemoteViews;
@@ -32,6 +39,8 @@
 import com.android.systemui.R;
 import com.android.systemui.people.PeopleSpaceUtils;
 
+import java.util.Collections;
+
 /** People Space Widget Provider class. */
 public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
     private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -88,11 +97,31 @@
     @Override
     public void onDeleted(Context context, int[] appWidgetIds) {
         super.onDeleted(context, appWidgetIds);
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+
         for (int widgetId : appWidgetIds) {
             if (DEBUG) Log.d(TAG, "Widget removed");
             mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
+            if (launcherApps != null) {
+                SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId),
+                        Context.MODE_PRIVATE);
+                String packageName = widgetSp.getString(PACKAGE_NAME, null);
+                String shortcutId = widgetSp.getString(SHORTCUT_ID, null);
+                int userId = widgetSp.getInt(USER_ID, -1);
+
+                if (packageName != null && shortcutId != null && userId != -1) {
+                    try {
+                        if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId);
+                        launcherApps.uncacheShortcuts(packageName,
+                                Collections.singletonList(shortcutId),
+                                UserHandle.of(userId),
+                                LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+                    } catch (Exception e) {
+                        Log.d(TAG, "Exception uncaching shortcut:" + e);
+                    }
+                }
+            }
             PeopleSpaceUtils.removeStorageForTile(context, widgetId);
         }
     }
-
 }
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 7820921..fcb35e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -311,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/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3866382..1411fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -170,7 +170,8 @@
         // Update visibility of footer
         mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
                 || vpnName != null || vpnNameWorkProfile != null
-                || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
+                || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
+                || (hasWorkProfile && isNetworkLoggingEnabled);
         // Update the string
         mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
                 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
@@ -275,12 +276,30 @@
                     vpnName);
         }
         if (isProfileOwnerOfOrganizationOwnedDevice) {
+            if (isNetworkLoggingEnabled) {
+                if (organizationName == null) {
+                    return mContext.getString(
+                            R.string.quick_settings_disclosure_management_monitoring);
+                }
+                return mContext.getString(
+                        R.string.quick_settings_disclosure_named_management_monitoring,
+                        organizationName);
+            }
             if (workProfileOrganizationName == null) {
                 return mContext.getString(R.string.quick_settings_disclosure_management);
             }
             return mContext.getString(R.string.quick_settings_disclosure_named_management,
                     workProfileOrganizationName);
         }
+        if (hasWorkProfile && isNetworkLoggingEnabled) {
+            if (workProfileOrganizationName == null) {
+                return mContext.getString(
+                        R.string.quick_settings_disclosure_managed_profile_monitoring);
+            }
+            return mContext.getString(
+                    R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+                    workProfileOrganizationName);
+        }
         return null;
     }
 
@@ -367,7 +386,8 @@
         }
 
         // network logging section
-        CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled);
+        CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
+                isNetworkLoggingEnabled);
         if (networkLoggingMessage == null) {
             dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
         } else {
@@ -492,9 +512,15 @@
         return mContext.getString(R.string.monitoring_description_ca_certificate);
     }
 
-    protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) {
+    protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
+            boolean isNetworkLoggingEnabled) {
         if (!isNetworkLoggingEnabled) return null;
-        return mContext.getString(R.string.monitoring_description_management_network_logging);
+        if (isDeviceManaged) {
+            return mContext.getString(R.string.monitoring_description_management_network_logging);
+        } else {
+            return mContext.getString(
+                    R.string.monitoring_description_managed_profile_network_logging);
+        }
     }
 
     protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
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 a567f51..aa6bbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -34,7 +34,7 @@
 
 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;
@@ -58,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;
@@ -153,7 +153,7 @@
                 }
             };
 
-    private static class Callback implements CarrierTextController.CarrierTextCallback {
+    private static class Callback implements CarrierTextManager.CarrierTextCallback {
         private H mHandler;
 
         Callback(H handler) {
@@ -161,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();
         }
     }
@@ -169,7 +169,7 @@
     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
-            CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+            CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
         if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
             mProviderModel = true;
         } else {
@@ -178,7 +178,7 @@
         mActivityStarter = activityStarter;
         mBgHandler = bgHandler;
         mNetworkController = networkController;
-        mCarrierTextController = carrierTextControllerBuilder
+        mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
                 .build();
@@ -196,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();
@@ -247,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);
         }
     }
 
@@ -277,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;
@@ -331,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;
@@ -349,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();
@@ -366,13 +365,13 @@
         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, Context context) {
+                CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
             mActivityStarter = activityStarter;
             mHandler = handler;
             mLooper = looper;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 6176a57..4144591 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -25,6 +25,7 @@
 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
@@ -91,7 +92,7 @@
     override fun isAvailable(): Boolean {
         return featureFlags.isKeyguardLayoutEnabled &&
                 controlsLockscreen &&
-                controlsComponent.getControlsUiController().isPresent
+                controlsComponent.getVisibility() != UNAVAILABLE
     }
 
     override fun newTileState(): QSTile.State {
@@ -154,4 +155,4 @@
     override fun getTileLabel(): CharSequence {
         return mContext.getText(R.string.quick_controls_title)
     }
-}
\ No newline at end of file
+}
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/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 9383aef..1386ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -24,15 +24,21 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
+import com.android.internal.widget.ExploreByTouchHelper;
 import com.android.systemui.R;
 
 /**
@@ -80,6 +86,8 @@
         t.recycle();
         // 48 dp touchable region around each handle.
         mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+
+        setAccessibilityDelegate(new AccessibilityHelper());
     }
 
     @Override
@@ -231,6 +239,87 @@
         return CropBoundary.NONE;
     }
 
+    private class AccessibilityHelper extends ExploreByTouchHelper {
+
+        private static final int TOP_HANDLE_ID = 1;
+        private static final int BOTTOM_HANDLE_ID = 2;
+
+        AccessibilityHelper() {
+            super(CropView.this);
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+                return TOP_HANDLE_ID;
+            }
+            if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+                return BOTTOM_HANDLE_ID;
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+            virtualViewIds.add(TOP_HANDLE_ID);
+            virtualViewIds.add(BOTTOM_HANDLE_ID);
+        }
+
+        @Override
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            switch(virtualViewId) {
+                case TOP_HANDLE_ID:
+                    event.setContentDescription(
+                            getResources().getString(R.string.screenshot_top_boundary));
+                    break;
+                case BOTTOM_HANDLE_ID:
+                    event.setContentDescription(
+                            getResources().getString(R.string.screenshot_bottom_boundary));
+                    break;
+            }
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(int virtualViewId,
+                AccessibilityNodeInfo node) {
+            switch(virtualViewId) {
+                case TOP_HANDLE_ID:
+                    node.setContentDescription(
+                            getResources().getString(R.string.screenshot_top_boundary));
+                    setNodePositions(mTopCrop, node);
+                    break;
+                case BOTTOM_HANDLE_ID:
+                    node.setContentDescription(
+                            getResources().getString(R.string.screenshot_bottom_boundary));
+                    setNodePositions(mBottomCrop, node);
+                    break;
+            }
+
+            // TODO: need to figure out the full set of actions to support here.
+            node.addAction(
+                    AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+            node.setClickable(true);
+            node.setFocusable(true);
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(
+                int virtualViewId, int action, Bundle arguments) {
+            return false;
+        }
+
+        private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
+            int pixels = fractionToPixels(fraction);
+            Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
+                    getWidth(), (int) (pixels + mCropTouchMargin));
+            node.setBoundsInParent(rect);
+            int[] pos = new int[2];
+            getLocationOnScreen(pos);
+            rect.offset(pos[0], pos[1]);
+            node.setBoundsInScreen(rect);
+        }
+    }
+
     /**
      * Listen for crop motion events and state.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index d56c806..dc639dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -95,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
+                    + '}';
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index d97f644..ad5e637 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -253,7 +253,7 @@
                 && result.captured.height() < result.requested.height();
         boolean finish = false;
 
-        if (partialResult) {
+        if (partialResult || emptyResult) {
             // Potentially reached a vertical boundary. Extend in the other direction.
             switch (mDirection) {
                 case DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index d707bca0..9f182e1 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -28,6 +28,7 @@
 import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
 import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
 import android.os.Bundle
+import android.os.Handler
 import android.text.Html
 import android.util.Log
 import com.android.internal.app.AlertActivity
@@ -43,10 +44,13 @@
 
     companion object {
         private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+
+        private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
     }
 
     private var sensor = -1
     private lateinit var sensorUsePackageName: String
+    private var unsuppressImmediately = false
 
     private lateinit var sensorPrivacyManager: SensorPrivacyManager
     private lateinit var appOpsManager: AppOpsManager
@@ -118,6 +122,7 @@
         super.onStart()
 
         sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true)
+        unsuppressImmediately = false
     }
 
     override fun onClick(dialog: DialogInterface?, which: Int) {
@@ -131,16 +136,16 @@
                         }
 
                         override fun onDismissSucceeded() {
-                            sensorPrivacyManager
-                                    .setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                            setResult(RESULT_OK)
+                            disableSensorPrivacy()
                         }
                     })
                 } else {
-                    sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                    setResult(RESULT_OK)
+                    disableSensorPrivacy()
                 }
             }
+            BUTTON_NEGATIVE -> {
+                unsuppressImmediately = false
+            }
         }
 
         dismiss()
@@ -149,10 +154,24 @@
     override fun onStop() {
         super.onDestroy()
 
-        sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+        if (unsuppressImmediately) {
+            sensorPrivacyManager
+                    .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+        } else {
+            Handler(mainLooper).postDelayed({
+                sensorPrivacyManager
+                        .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+            }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS)
+        }
     }
 
     override fun onBackPressed() {
         // do not allow backing out
     }
+
+    private fun disableSensorPrivacy() {
+        sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+        unsuppressImmediately = true
+        setResult(RESULT_OK)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 7aa41e4..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -67,6 +67,10 @@
         return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
     }
 
+    public boolean useNewLockscreenAnimations() {
+        return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
+    }
+
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c70a93b..7e2d27a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -208,7 +208,6 @@
                 mLockScreenMode);
         updateIndication(false /* animate */);
         updateDisclosure();
-        updateOwnerInfo();
         if (mBroadcastReceiver == null) {
             // Update the disclosure proactively to avoid IPC on the critical path.
             mBroadcastReceiver = new BroadcastReceiver() {
@@ -261,18 +260,21 @@
     }
 
     /**
-     * Doesn't include owner information or disclosure which get triggered separately.
+     * Doesn't include disclosure which gets triggered separately.
      */
     private void updateIndications(boolean animate, int userId) {
+        updateOwnerInfo();
         updateBattery(animate);
         updateUserLocked(userId);
         updateTransient();
         updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
         updateAlignment();
+        updateLogoutView();
         updateResting();
     }
 
     private void updateDisclosure() {
+        // avoid calling this method since it has an IPC
         if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
             final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
             final CharSequence disclosure =  organizationName != null
@@ -291,7 +293,34 @@
         }
 
         if (isKeyguardLayoutEnabled()) {
-            updateIndication(false); // resting indication may need to update
+            updateResting();
+        }
+    }
+
+    private void updateOwnerInfo() {
+        if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
+            return;
+        }
+        String info = mLockPatternUtils.getDeviceOwnerInfo();
+        if (info == null) {
+            // Use the current user owner information if enabled.
+            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            if (ownerInfoEnabled) {
+                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+            }
+        }
+        if (info != null) {
+            mRotateTextViewController.updateIndication(
+                    INDICATION_TYPE_OWNER_INFO,
+                    new KeyguardIndication.Builder()
+                            .setMessage(info)
+                            .setTextColor(mInitialTextColorState)
+                            .build(),
+                    false);
+        } else {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
         }
     }
 
@@ -400,56 +429,34 @@
 
     private void updateLogoutView() {
         if (!isKeyguardLayoutEnabled()) {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
             return;
         }
         final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-        String logoutString = shouldShowLogout ? mContext.getResources().getString(
-                    com.android.internal.R.string.global_action_logout) : null;
-        mRotateTextViewController.updateIndication(
-                INDICATION_TYPE_LOGOUT,
-                new KeyguardIndication.Builder()
-                        .setMessage(logoutString)
-                        .setTextColor(mInitialTextColorState)
-                        .setBackground(mContext.getDrawable(
-                                com.android.systemui.R.drawable.logout_button_background))
-                        .setClickListener((view) -> {
-                            int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
-                            try {
-                                mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
-                                mIActivityManager.stopUser(currentUserId, true /* force */, null);
-                            } catch (RemoteException re) {
-                                Log.e(TAG, "Failed to logout user", re);
-                            }
-                        })
-                .build(),
-                false);
-        updateIndication(false); // resting indication may need to update
-    }
-
-    private void updateOwnerInfo() {
-        if (!isKeyguardLayoutEnabled()) {
-            return;
-        }
-        String info = mLockPatternUtils.getDeviceOwnerInfo();
-        if (info == null) {
-            // Use the current user owner information if enabled.
-            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            if (ownerInfoEnabled) {
-                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
-            }
-        }
-        if (info != null) {
+        if (shouldShowLogout) {
             mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_OWNER_INFO,
+                    INDICATION_TYPE_LOGOUT,
                     new KeyguardIndication.Builder()
-                            .setMessage(info)
+                            .setMessage(mContext.getResources().getString(
+                                    com.android.internal.R.string.global_action_logout))
                             .setTextColor(mInitialTextColorState)
+                            .setBackground(mContext.getDrawable(
+                                    com.android.systemui.R.drawable.logout_button_background))
+                            .setClickListener((view) -> {
+                                int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+                                try {
+                                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                                    mIActivityManager.stopUser(currentUserId, true /* force */,
+                                            null);
+                                } catch (RemoteException re) {
+                                    Log.e(TAG, "Failed to logout user", re);
+                                }
+                            })
                             .build(),
                     false);
         } else {
-            updateIndication(false); // resting indication may need to update
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
         }
     }
 
@@ -1042,7 +1049,6 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (mVisible) {
-                updateOwnerInfo();
                 updateIndication(false);
             }
         }
@@ -1057,7 +1063,7 @@
         @Override
         public void onLogoutEnabledChanged() {
             if (mVisible) {
-                updateLogoutView();
+                updateIndication(false);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2f0f90d..c1feaca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,14 +11,10 @@
 import android.graphics.PorterDuffXfermode
 import android.graphics.RadialGradient
 import android.graphics.Shader
-import android.os.SystemProperties
 import android.util.AttributeSet
 import android.view.View
 import com.android.systemui.Interpolators
 
-val enableLightReveal =
-        SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false)
-
 /**
  * Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
  * 100% of the view(s) underneath the scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 8c2fa33..85d8df8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -54,6 +55,7 @@
     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
     private final Resources mResources;
     private final BatteryController mBatteryController;
+    private final FeatureFlags mFeatureFlags;
 
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
@@ -65,7 +67,8 @@
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             PowerManager powerManager,
             BatteryController batteryController,
-            TunerService tunerService) {
+            TunerService tunerService,
+            FeatureFlags featureFlags) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -74,6 +77,7 @@
         mControlScreenOffAnimation = !getDisplayNeedsBlanking();
         mPowerManager = powerManager;
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
+        mFeatureFlags = featureFlags;
 
         tunerService.addTunable(
                 this,
@@ -200,8 +204,7 @@
      * then abruptly showing AOD.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return getAlwaysOn() && SystemProperties.getBoolean(
-                "persist.sysui.show_new_screen_on_transitions", false);
+        return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
     }
 
     private boolean getBoolean(String propName, int resId) {
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 e0df4f8..a6daed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -56,10 +56,12 @@
     private int mKeyguardStatusHeight;
 
     /**
-     * Height of {@link KeyguardUserSwitcherListView} when it
-     * is closed and only the current user's icon is visible.
+     * 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 mKeyguardUserSwitcherHeight;
+    private int mUserSwitchHeight;
 
     /**
      * Preferred Y position of clock.
@@ -67,6 +69,11 @@
     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;
@@ -181,20 +188,21 @@
      */
     public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
             float panelExpansion, int parentHeight, int keyguardStatusHeight,
-            int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
-            boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
-            int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
-            int cutoutTopInset) {
+            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)
-                + keyguardUserSwitcherHeight;
+                + userSwitchHeight;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
-        mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
+        mUserSwitchHeight = userSwitchHeight;
         mClockPreferredY = clockPreferredY;
+        mUserSwitchPreferredY = userSwitchPreferredY;
         mHasCustomClock = hasCustomClock;
         mHasVisibleNotifs = hasVisibleNotifs;
         mDarkAmount = dark;
@@ -208,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);
@@ -241,7 +250,7 @@
 
     private int getExpandedPreferredClockY() {
         if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
-            return mMinTopMargin;
+            return mMinTopMargin + mUserSwitchHeight;
         }
         return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
                 : getExpandedClockPosition();
@@ -257,7 +266,7 @@
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
         float y = containerCenter
-                - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
+                - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT
                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
         if (y < mMinTopMargin) {
             y = mMinTopMargin;
@@ -299,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
@@ -341,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/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 d9cb9ce..16f36b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -168,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 3b09eda..2f9fa9e 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;
@@ -64,7 +68,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 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;
@@ -77,6 +82,8 @@
 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;
@@ -95,9 +102,11 @@
 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;
+import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -133,6 +142,7 @@
 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.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
@@ -292,14 +302,13 @@
             new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
                 @Override
                 public void onKeyguardUserSwitcherChanged(boolean open) {
-                    if (mKeyguardUserSwitcherController != null
-                            && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
-                        return;
+                    if (mKeyguardUserSwitcherController == null) {
+                        updateUserSwitcherVisibility(false);
+                    } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+                        updateUserSwitcherVisibility(open
+                                && mKeyguardStateController.isShowing()
+                                && !mKeyguardStateController.isKeyguardFadingAway());
                     }
-
-                    updateUserSwitcherVisibility(open
-                            && mKeyguardStateController.isShowing()
-                            && !mKeyguardStateController.isKeyguardFadingAway());
                 }
             };
 
@@ -315,7 +324,10 @@
     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;
     private final ControlsComponent mControlsComponent;
@@ -327,8 +339,11 @@
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
+    private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+    private boolean mKeyguardUserSwitcherIsShowing;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
+    private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -352,6 +367,7 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardQsUserSwitchEnabled;
     private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
@@ -388,6 +404,7 @@
     // Used for two finger gesture as well as accessibility shortcut to QS.
     private boolean mQsExpandImmediate;
     private boolean mTwoFingerQsExpandPossible;
+    private String mHeaderDebugInfo;
 
     /**
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
@@ -578,7 +595,10 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+            KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
+            QSDetailDisplayer qsDetailDisplayer,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
@@ -604,10 +624,16 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+        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;
@@ -688,14 +714,22 @@
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
 
+        UserAvatarView userAvatarView = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
 
         if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
-            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
-            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+            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(
@@ -741,6 +775,10 @@
         });
 
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
+        // dynamically apply the split shade value overrides.
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+            updateResources();
+        }
     }
 
     @Override
@@ -770,6 +808,8 @@
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+            UserAvatarView userAvatarView,
+            KeyguardStatusBarView keyguardStatusBarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
@@ -777,6 +817,12 @@
         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();
@@ -789,18 +835,27 @@
             mKeyguardUserSwitcherController.removeCallback();
         }
 
+        mKeyguardQsUserSwitchController = null;
+        mKeyguardUserSwitcherController = null;
+
         // Re-associate the KeyguardUserSwitcherController
-        if (keyguardUserSwitcherView != null) {
+        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 {
-            mKeyguardUserSwitcherController = null;
             mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
         }
     }
@@ -840,11 +895,21 @@
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
 
+        // 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)) {
-            // In order to change the constraints at runtime, all children of the Constraint Layout
-            // must have ids.
-            ensureAllViewsHaveIds(mNotificationContainerParent);
+            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);
         }
+        constraintSet.applyTo(mNotificationContainerParent);
     }
 
     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
@@ -856,6 +921,25 @@
         }
     }
 
+    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.
@@ -867,26 +951,27 @@
         mView.addView(keyguardStatusView, index);
 
         // Re-inflate the keyguard user switcher group.
-        boolean showUserSwitcher =
-                mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
-        KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
-                R.id.keyguard_user_switcher_view);
-        if (keyguardUserSwitcherView != null) {
-            index = mView.indexOfChild(keyguardUserSwitcherView);
-            mView.removeView(keyguardUserSwitcherView);
-            if (showUserSwitcher) {
-                keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
-                        R.layout.keyguard_user_switcher, mView, false);
-                mView.addView(keyguardUserSwitcherView, index);
-            }
-        } else if (showUserSwitcher) {
-            // It's possible the user switcher was never inflated if the configuration changed
-            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
-            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
-        }
+        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, keyguardUserSwitcherView);
+        updateViewControllers(
+                keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
@@ -910,6 +995,13 @@
                 false,
                 false,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
                     mBarState,
@@ -1009,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(),
@@ -1021,9 +1118,8 @@
                             ? mKeyguardStatusViewController.getHeight()
                             : (int) (mKeyguardStatusViewController.getHeight()
                                     - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
-                    mKeyguardUserSwitcherController == null
-                            ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
-                    clockPreferredY, hasCustomClock(),
+                    userIconHeight,
+                    clockPreferredY, userSwitcherPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
                     mUpdateMonitor.canShowLockIcon(),
@@ -1033,11 +1129,16 @@
             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.clockY
-                                - mKeyguardUserSwitcherController.getUserIconHeight(),
+                        mClockPositionResult.userSwitchY,
                         animateClock);
             }
             updateNotificationTranslucency();
@@ -1180,6 +1281,9 @@
 
     private void updateClock() {
         mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
         }
@@ -1271,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);
@@ -1933,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)) {
 
@@ -2572,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();
@@ -2814,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;
@@ -3166,6 +3282,13 @@
                 true /* keyguardFadingAway */,
                 false /* goingToFullShade */,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
                     mBarState,
@@ -3300,8 +3423,8 @@
         return mView.getHeight();
     }
 
-    public TextView getHeaderDebugInfo() {
-        return mView.findViewById(R.id.header_debug_info);
+    public void setHeaderDebugInfo(String text) {
+        if (DEBUG) mHeaderDebugInfo = text;
     }
 
     public void onThemeChanged() {
@@ -3396,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);
@@ -3432,6 +3555,12 @@
     }
 
     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(
@@ -3958,6 +4087,8 @@
             p.setStrokeWidth(2);
             p.setStyle(Paint.Style.STROKE);
             canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+            p.setTextSize(24);
+            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
             p.setColor(Color.BLUE);
             canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
             p.setColor(Color.GREEN);
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 e63902f..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -35,7 +35,6 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -180,6 +179,7 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -440,6 +440,7 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
+    private final FeatureFlags mFeatureFlags;
 
     private final List<ExpansionChangedListener> mExpansionChangedListeners;
 
@@ -762,7 +763,8 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory) {
+            BrightnessSlider.Factory brightnessSliderFactory,
+            FeatureFlags featureFlags) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -840,6 +842,7 @@
         mDemoModeController = demoModeController;
         mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
+        mFeatureFlags = featureFlags;
 
         mExpansionChangedListeners = new ArrayList<>();
 
@@ -1181,9 +1184,11 @@
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
 
-        if (getEnableLightReveal()) {
+        if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) {
             mLightRevealScrim.setVisibility(View.VISIBLE);
             mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+        } else {
+            mLightRevealScrim.setVisibility(View.GONE);
         }
 
         mNotificationPanelViewController.initDependencies(
@@ -3614,7 +3619,7 @@
 
     @Override
     public void onDozeAmountChanged(float linear, float eased) {
-        if (getEnableLightReveal()) {
+        if (mFeatureFlags.useNewLockscreenAnimations()) {
             mLightRevealScrim.setRevealAmount(1f - linear);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 024a0b1..525f220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -29,7 +29,6 @@
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
-import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -162,11 +161,6 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
-        if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
-            mNotificationPanelDebugText.setVisibility(View.VISIBLE);
-        }
-
         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
                 Context.VR_SERVICE));
         if (vrManager != null) {
@@ -332,7 +326,7 @@
         // Begin old BaseStatusBar.userSwitched
         mHeadsUpManager.setUser(newUserId);
         // End old BaseStatusBar.userSwitched
-        if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+        if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
         mCommandQueue.animateCollapsePanels();
         if (mReinflateNotificationsOnUserSwitched) {
             updateNotificationsOnDensityOrFontScaleChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 9e9533d..b572c57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -200,7 +201,8 @@
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory) {
+            BrightnessSlider.Factory brightnessSliderFactory,
+            FeatureFlags featureFlags) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -279,6 +281,7 @@
                 notificationShadeDepthController,
                 statusBarTouchableRegionManager,
                 notificationIconAreaController,
-                brightnessSliderFactory);
+                brightnessSliderFactory,
+                featureFlags);
     }
 }
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/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 019ab47..d4029e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -121,6 +121,7 @@
     private Intent mSecondaryUserServiceIntent;
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
     private final UiEventLogger mUiEventLogger;
+    public final DetailAdapter mUserDetailAdapter;
 
     @Inject
     public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@@ -131,6 +132,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mActivityTaskManager = activityTaskManager;
         mUiEventLogger = uiEventLogger;
+        mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
         }
@@ -781,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);
@@ -794,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/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 9e78a66..0a3e833 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,6 +65,8 @@
             "android.theme.customization.accent_color";
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
+    static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE =
+            "android.theme.customization.neutral_palette";
     @VisibleForTesting
     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 522a42b..1f222d8 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,6 +16,7 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
 import android.annotation.Nullable;
@@ -83,8 +84,9 @@
     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;
+    protected static final int PRIMARY = 0;
+    protected static final int SECONDARY = 1;
+    protected static final int NEUTRAL = 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
@@ -111,9 +113,11 @@
     // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
     // System colors overlay
-    private FabricatedOverlay mSystemOverlay;
+    private FabricatedOverlay mPrimaryOverlay;
     // Accent colors overlay
-    private FabricatedOverlay mAccentOverlay;
+    private FabricatedOverlay mSecondaryOverlay;
+    // Neutral system colors overlay
+    private FabricatedOverlay mNeutralOverlay;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -232,12 +236,13 @@
         mWallpaperAccentColor = accentCandidate;
 
         if (mIsMonetEnabled) {
-            mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
-            mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+            mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY);
+            mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY);
+            mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
             mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
-                        + mAccentOverlay);
+                Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: "
+                        + mSecondaryOverlay + " neutral: " + mNeutralOverlay);
             }
         }
 
@@ -296,7 +301,9 @@
         if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
-                mSystemOverlay = getOverlay(color, MAIN);
+                mPrimaryOverlay = getOverlay(color, PRIMARY);
+                // Neutral palette is always derived from primary color.
+                mNeutralOverlay = getOverlay(color, NEUTRAL);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
             } catch (NumberFormatException e) {
@@ -309,7 +316,7 @@
         if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
-                mAccentOverlay = getOverlay(color, ACCENT);
+                mSecondaryOverlay = getOverlay(color, SECONDARY);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
             } catch (NumberFormatException e) {
@@ -320,12 +327,14 @@
         // 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());
+                && mPrimaryOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier());
+            categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE,
+                    mNeutralOverlay.getIdentifier());
         }
         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
-                && mAccentOverlay != null) {
-            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
+                && mSecondaryOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier());
         }
 
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -342,7 +351,7 @@
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
-                    mSystemOverlay, mAccentOverlay
+                    mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay
             }, userHandles);
         } else {
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
@@ -356,8 +365,9 @@
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mSystemOverlayColor=" + mSystemOverlay);
-        pw.println("mAccentOverlayColor=" + mAccentOverlay);
+        pw.println("mPrimaryOverlay=" + mPrimaryOverlay);
+        pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
+        pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec61db5..8505703 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -366,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 449db61..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;
@@ -73,7 +72,6 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
-import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -211,8 +209,8 @@
     @Provides
     static SizeCompatUIController provideSizeCompatUIController(Context context,
             DisplayController displayController, DisplayImeController imeController,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+            SyncTransactionQueue syncQueue) {
+        return new SizeCompatUIController(context, displayController, imeController, syncQueue);
     }
 
     @WMSingleton
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/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/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/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/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
new file mode 100644
index 0000000..b44fb8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.keyguard;
+
+import static android.graphics.Color.WHITE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardIndicationTest extends SysuiTestCase {
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutMessageOrIcon() {
+        new KeyguardIndication.Builder()
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithoutColor() {
+        new KeyguardIndication.Builder()
+                .setMessage("message")
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCannotCreateIndicationWithEmptyMessage() {
+        new KeyguardIndication.Builder()
+                .setMessage("")
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+    }
+
+    @Test
+    public void testCreateIndicationWithMessage() {
+        final String text = "regular indication";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+    }
+
+    @Test
+    public void testCreateIndicationWithIcon() {
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    @Test
+    public void testCreateIndicationWithMessageAndIcon() {
+        final String text = "indication with msg and icon";
+        final KeyguardIndication indication = new KeyguardIndication.Builder()
+                .setMessage(text)
+                .setIcon(mDrawable)
+                .setTextColor(ColorStateList.valueOf(WHITE))
+                .build();
+        assertEquals(text, indication.getMessage());
+        assertEquals(mDrawable, indication.getIcon());
+    }
+
+    final Drawable mDrawable = new Drawable() {
+        @Override
+        public void draw(@NonNull Canvas canvas) { }
+
+        @Override
+        public void setAlpha(int alpha) { }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) { }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    };
+}
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/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 4ee2759..d79155c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,11 +16,18 @@
 
 package com.android.systemui.people;
 
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+
 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;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +46,7 @@
 import android.app.NotificationManager;
 import android.app.Person;
 import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -46,13 +54,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.database.Cursor;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.ContactsContract;
 import android.provider.Settings;
@@ -60,6 +68,9 @@
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.systemui.R;
@@ -101,27 +112,45 @@
     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);
+    private static final String GAME_DESCRIPTION = "Playing a game!";
+    private static final String NAME = "username";
     private static final Person PERSON = new Person.Builder()
             .setName("name")
             .setKey("abc")
             .setUri(URI.toString())
             .setBot(false)
             .build();
+    private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+            new PeopleSpaceTile
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(0L)
+                    .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
-                    .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(123L)
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
+    private static final ConversationStatus GAME_STATUS =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+                    .setDescription(GAME_DESCRIPTION)
+                    .build();
+    private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+                    .setAvailability(AVAILABILITY_AVAILABLE)
+                    .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
             SHORTCUT_ID_1).setLongLabel(
-            "name").setPerson(PERSON)
+            NAME).setPerson(PERSON)
             .build();
     private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
             SHORTCUT_ID_1).setLongLabel(
-            "name")
+            NAME)
             .build();
     private final Notification mNotification1 = new Notification.Builder(mContext, "test")
             .setContentTitle("TEST_TITLE")
@@ -189,10 +218,12 @@
     @Mock
     private Context mMockContext;
     @Mock
+    private PackageManager mPackageManager;
+    @Mock
     private NotificationEntryManager mNotificationEntryManager;
 
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
@@ -212,6 +243,12 @@
                 isNull())).thenReturn(mMockCursor);
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
+        when(mMockContext.getString(R.string.basic_status)).thenReturn(
+                mContext.getString(R.string.basic_status));
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+                mContext.getString(R.string.over_timestamp));
+        when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
         when(mNotificationEntryManager.getVisibleNotifications())
                 .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
     }
@@ -621,6 +658,137 @@
                 any());
     }
 
+    @Test
+    public void testCreateRemoteViewsWithLastInteractionTime() {
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // No availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.GONE, availability.getVisibility());
+        // No new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        assertEquals(View.GONE, personIconWithStory.getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.status)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+        PeopleSpaceTile tileWithAvailabilityAndNewStory =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+                                new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_GAME).build())).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+                tileWithAvailabilityAndNewStory, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction over status.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.status)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(
+                                NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_BIRTHDAY).build())).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusTemplate, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has status text from backup text.
+        TextView statusContent = (TextView) result.findViewById(R.id.status);
+        assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithStatusTemplate() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(GAME_STATUS,
+                                NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusTemplate, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has status.
+        TextView statusContent = (TextView) result.findViewById(R.id.status);
+        assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithNotificationTemplate() {
+        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+                .setNotificationDataUri(null)
+                .setStatuses(Arrays.asList(GAME_STATUS,
+                        NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+                tileWithStatusAndNotification, 0);
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        TextView subtext = (TextView) result.findViewById(R.id.subtext);
+        assertTrue(subtext.getText().toString().contains("weeks ago"));
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has new story.
+        View personIcon = result.findViewById(R.id.person_icon_only);
+        View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+        assertEquals(View.GONE, personIcon.getVisibility());
+        assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+        // Has notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.content);
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+    }
+
     private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
             boolean importantConversation, long lastInteractionTimestamp) throws Exception {
         ConversationChannelWrapper convo = new ConversationChannelWrapper();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index fd0715b..862e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -443,9 +443,18 @@
 
     @Test
     public void testGetNetworkLoggingMessage() {
-        assertEquals(null, mFooter.getNetworkLoggingMessage(false));
+        // Test network logging message on a device with a device owner.
+        // Network traffic may be monitored on the device.
+        assertEquals(null, mFooter.getNetworkLoggingMessage(true, false));
         assertEquals(mContext.getString(R.string.monitoring_description_management_network_logging),
-                     mFooter.getNetworkLoggingMessage(true));
+                mFooter.getNetworkLoggingMessage(true, true));
+
+        // Test network logging message on a device with a managed profile owner
+        // Network traffic may be monitored on the work profile.
+        assertEquals(null, mFooter.getNetworkLoggingMessage(false, false));
+        assertEquals(
+                mContext.getString(R.string.monitoring_description_managed_profile_network_logging),
+                mFooter.getNetworkLoggingMessage(false, true));
     }
 
     @Test
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 1ec1da4..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));
@@ -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
index b7b9678..d3dbe2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -25,6 +25,7 @@
 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
@@ -36,15 +37,19 @@
 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
@@ -91,6 +96,15 @@
     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)
@@ -101,9 +115,14 @@
 
         controlsComponent = ControlsComponent(
                 true,
+                mContext,
                 { controlsController },
                 { controlsUiController },
-                { controlsListingController }
+                { controlsListingController },
+                lockPatternUtils,
+                keyguardStateController,
+                userTracker,
+                secureSettings
         )
 
         globalSettings = FakeSettings()
@@ -111,16 +130,20 @@
         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()
@@ -130,9 +153,14 @@
     fun testNotAvailableControls() {
         controlsComponent = ControlsComponent(
                 false,
+                mContext,
                 { controlsController },
                 { controlsUiController },
-                { controlsListingController }
+                { controlsListingController },
+                lockPatternUtils,
+                keyguardStateController,
+                userTracker,
+                secureSettings
         )
         tile = createTile()
 
@@ -264,4 +292,4 @@
                 globalSettings
         )
     }
-}
\ No newline at end of file
+}
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 fa253e6..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;
@@ -35,6 +37,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -57,6 +60,7 @@
     @Mock private PowerManager mPowerManager;
     @Mock private TunerService mTunerService;
     @Mock private BatteryController mBatteryController;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setup() {
@@ -67,7 +71,8 @@
             mAlwaysOnDisplayPolicy,
             mPowerManager,
             mBatteryController,
-            mTunerService
+            mTunerService,
+            mFeatureFlags
         );
     }
     @Test
@@ -111,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 4162884..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
@@ -394,8 +394,8 @@
     private void positionClock() {
         mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
                 mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
-                0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
-                mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+                0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */,
+                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
                 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 d0e7031..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
@@ -48,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;
@@ -58,6 +60,8 @@
 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;
@@ -70,6 +74,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -194,14 +199,24 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
-    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+    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;
@@ -216,14 +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() {
@@ -256,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(
@@ -289,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,
@@ -305,7 +324,10 @@
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
-                mKeyguardUserSwitcherComponent,
+                mKeyguardQsUserSwitchComponentFactory,
+                mKeyguardUserSwitcherComponentFactory,
+                mKeyguardStatusBarViewComponentFactory,
+                mQSDetailDisplayer,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
@@ -432,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();
 
@@ -449,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/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index cae488a..253460d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -258,6 +259,7 @@
     @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
+    @Mock private FeatureFlags mFeatureFlags;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -418,7 +420,8 @@
                 mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager,
                 mNotificationIconAreaController,
-                mBrightnessSliderFactory);
+                mBrightnessSliderFactory,
+                mFeatureFlags);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
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 f7f8d03..aa385ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
 
@@ -147,6 +148,8 @@
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
                 .isEqualTo(new OverlayIdentifier("ffff0000"));
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
                 .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
diff --git a/packages/services/CameraExtensionsProxy/Android.bp b/packages/services/CameraExtensionsProxy/Android.bp
index 54b7453..e2e4af2 100644
--- a/packages/services/CameraExtensionsProxy/Android.bp
+++ b/packages/services/CameraExtensionsProxy/Android.bp
@@ -18,6 +18,7 @@
     name: "CameraExtensionsProxy",
     srcs: ["src/**/*.java"],
     libs: ["androidx.camera.extensions.stub"],
+    optional_uses_libs: ["androidx.camera.extensions.impl"],
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/services/Android.bp b/services/Android.bp
index 61591c2..8aae8e6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -6,7 +6,7 @@
 }
 
 filegroup {
-    name: "services-all-sources",
+    name: "services-non-updatable-sources",
     srcs: [
         ":services.core-sources",
         ":services.core-sources-am-wm",
@@ -37,7 +37,15 @@
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
         ":services.wifi-sources",
-        ":service-media-s-sources", // TODO (b/177640454)
+    ],
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services-all-sources",
+    srcs: [
+        ":services-non-updatable-sources",
+        ":service-media-s-sources",
         ":service-permission-sources",
         ":service-statsd-sources",
     ],
@@ -123,9 +131,8 @@
 // API stub
 // =============================================================
 
-droidstubs {
-    name: "services-stubs.sources",
-    srcs: [":services-all-sources"],
+stubs_defaults {
+    name: "services-stubs-default",
     installable: false,
     args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
         " --hide-annotation android.annotation.Hide" +
@@ -135,7 +142,13 @@
         " --hide DeprecationMismatch" +
         " --hide HiddenTypedefConstant",
     visibility: ["//visibility:private"],
-    filter_packages: ["com.android."],
+    filter_packages: ["com.android."]
+}
+
+droidstubs {
+    name: "services-stubs.sources",
+    srcs: [":services-all-sources"],
+    defaults: ["services-stubs-default"],
     check_api: {
         current: {
             api_file: "api/current.txt",
@@ -181,3 +194,34 @@
         dir: "apistubs/android/system-server",
     },
 }
+
+droidstubs {
+    name: "services-non-updatable-stubs.sources",
+    srcs: [":services-non-updatable-sources"],
+    defaults: ["services-stubs-default"],
+    check_api: {
+        current: {
+            api_file: "api/non-updatable-current.txt",
+            removed_api_file: "api/non-updatable-removed.txt",
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":android-non-updatable.api.system-server.latest",
+            baseline_file: "api/non-updatable-lint-baseline.txt",
+        },
+    },
+    dists: [
+        {
+            targets: ["sdk", "win_sdk"],
+            dir: "apistubs/android/system-server/api",
+            dest: "android-non-updatable.txt",
+            tag: ".api.txt"
+        },
+        {
+            targets: ["sdk", "win_sdk"],
+            dir: "apistubs/android/system-server/api",
+            dest: "android-non-updatable-removed.txt",
+            tag: ".removed-api.txt",
+        },
+    ]
+}
\ No newline at end of file
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/api/non-updatable-current.txt b/services/api/non-updatable-current.txt
new file mode 100644
index 0000000..3c72d38
--- /dev/null
+++ b/services/api/non-updatable-current.txt
@@ -0,0 +1,55 @@
+// Signature format: 2.0
+package com.android.server {
+
+  public final class LocalManagerRegistry {
+    method public static <T> void addManager(@NonNull Class<T>, @NonNull T);
+    method @Nullable public static <T> T getManager(@NonNull Class<T>);
+  }
+
+  public abstract class SystemService {
+    ctor public SystemService(@NonNull android.content.Context);
+    method @NonNull public final android.content.Context getContext();
+    method public boolean isUserSupported(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onBootPhase(int);
+    method public abstract void onStart();
+    method public void onUserStarting(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserStopped(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserStopping(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserSwitching(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserUnlocked(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onUserUnlocking(@NonNull com.android.server.SystemService.TargetUser);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+    field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+    field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+    field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+    field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+    field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+    field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+    field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+  }
+
+  public static final class SystemService.TargetUser {
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+}
+
+package com.android.server.role {
+
+  public interface RoleServicePlatformHelper {
+    method @NonNull public String computePackageStateHash(int);
+    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getLegacyRoleState(int);
+  }
+
+}
+
+package com.android.server.wifi {
+
+  public class SupplicantManager {
+    method public static void start();
+    method public static void stop();
+  }
+
+}
+
diff --git a/services/api/non-updatable-lint-baseline.txt b/services/api/non-updatable-lint-baseline.txt
new file mode 100644
index 0000000..b46d21e
--- /dev/null
+++ b/services/api/non-updatable-lint-baseline.txt
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+NotCloseable: com.android.server.wifi.SupplicantManager:
+    Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class com.android.server.wifi.SupplicantManager
+
+
+ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder):
+    Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder)}
+ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder, boolean):
+    Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder,boolean)}
diff --git a/services/api/non-updatable-removed.txt b/services/api/non-updatable-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/api/non-updatable-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index eff410c..809304b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2624,12 +2624,26 @@
             info.minWidth = value != null ? value.data : 0;
             value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
             info.minHeight = value != null ? value.data : 0;
+
             value = sa.peekValue(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth);
             info.minResizeWidth = value != null ? value.data : info.minWidth;
             value = sa.peekValue(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight);
             info.minResizeHeight = value != null ? value.data : info.minHeight;
+
+            value = sa.peekValue(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeWidth);
+            info.maxResizeWidth = value != null ? value.data : 0;
+            value = sa.peekValue(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeHeight);
+            info.maxResizeHeight = value != null ? value.data : 0;
+
+            info.targetCellWidth = sa.getInt(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellWidth, 0);
+            info.targetCellHeight = sa.getInt(
+                    com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellHeight, 0);
+
             info.updatePeriodMillis = sa.getInt(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
             info.initialLayout = sa.getResourceId(
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 10b00d3..76c8d30 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -257,6 +257,8 @@
         new PackageMonitor() {
             @Override
             public void onPackageRemoved(String packageName, int uid) {
+                Slog.d(LOG_TAG, "onPackageRemoved(packageName = " + packageName
+                        + ", uid = " + uid + ")");
                 int userId = getChangingUserId();
                 updateAssociations(
                         as -> CollectionUtils.filter(as,
@@ -268,6 +270,7 @@
 
             @Override
             public void onPackageModified(String packageName) {
+                Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")");
                 int userId = getChangingUserId();
                 forEach(getAllAssociations(userId, packageName), association -> {
                     updateSpecialAccessPermissionForAssociatedPackage(association);
@@ -304,7 +307,7 @@
                         mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter);
                 initBleScanning();
             } else {
-                Log.w(LOG_TAG, "No BluetoothAdapter available");
+                Slog.w(LOG_TAG, "No BluetoothAdapter available");
             }
         }
     }
@@ -324,6 +327,7 @@
     }
 
     void maybeGrantAutoRevokeExemptions() {
+        Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
         PackageManager pm = getContext().getPackageManager();
         for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
             SharedPreferences pref = getContext().getSharedPreferences(
@@ -343,7 +347,7 @@
                         int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
                         exemptFromAutoRevoke(a.getPackageName(), uid);
                     } catch (PackageManager.NameNotFoundException e) {
-                        Log.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e);
+                        Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e);
                     }
                 }
             } finally {
@@ -354,10 +358,13 @@
 
     @Override
     public void binderDied() {
+        Slog.w(LOG_TAG, "binderDied()");
         mMainHandler.post(this::cleanup);
     }
 
     private void cleanup() {
+        Slog.d(LOG_TAG, "cleanup(); discovery = "
+                + mOngoingDeviceDiscovery + ", request = " + mRequest);
         synchronized (mLock) {
             AndroidFuture<Association> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
             if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
@@ -400,10 +407,8 @@
                 AssociationRequest request,
                 IFindDeviceCallback callback,
                 String callingPackage) throws RemoteException {
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
-                        + ", callingPackage = " + callingPackage + ")");
-            }
+            Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+                    + ", callingPackage = " + callingPackage + ")");
             checkNotNull(request, "Request cannot be null");
             checkNotNull(callback, "Callback cannot be null");
             checkCallerIsSystemOr(callingPackage);
@@ -423,9 +428,13 @@
                                     request.getDeviceProfile());
 
             mOngoingDeviceDiscovery = fetchProfileDescription.thenComposeAsync(description -> {
+                Slog.d(LOG_TAG, "fetchProfileDescription done: " + description);
+
                 request.setDeviceProfilePrivilegesDescription(description);
 
                 return mServiceConnectors.forUser(userId).postAsync(service -> {
+                    Slog.d(LOG_TAG, "Connected to CDM service; starting discovery for " + request);
+
                     AndroidFuture<Association> future = new AndroidFuture<>();
                     service.startDiscovery(request, callingPackage, callback, future);
                     return future;
@@ -438,7 +447,7 @@
                     if (err == null) {
                         addAssociation(association);
                     } else {
-                        Log.e(LOG_TAG, "Failed to discover device(s)", err);
+                        Slog.e(LOG_TAG, "Failed to discover device(s)", err);
                         callback.onFailure("No devices found: " + err.getMessage());
                     }
                     cleanup();
@@ -452,6 +461,7 @@
         public void stopScan(AssociationRequest request,
                 IFindDeviceCallback callback,
                 String callingPackage) {
+            Slog.d(LOG_TAG, "stopScan(request = " + request + ")");
             if (Objects.equals(request, mRequest)
                     && Objects.equals(callback, mFindDeviceCallback)
                     && Objects.equals(callingPackage, mCallingPackage)) {
@@ -712,7 +722,7 @@
                     getAllAssociations(association.getUserId()),
                     a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
             if (otherAssociationWithDeviceProfile != null) {
-                Log.i(LOG_TAG, "Not revoking " + deviceProfile
+                Slog.i(LOG_TAG, "Not revoking " + deviceProfile
                         + " for " + association
                         + " - profile still present in " + otherAssociationWithDeviceProfile);
             } else {
@@ -726,7 +736,7 @@
                             getContext().getMainExecutor(),
                             success -> {
                                 if (!success) {
-                                    Log.e(LOG_TAG, "Failed to revoke device profile role "
+                                    Slog.e(LOG_TAG, "Failed to revoke device profile role "
                                             + association.getDeviceProfile()
                                             + " to " + association.getPackageName()
                                             + " for user " + association.getUserId());
@@ -794,7 +804,7 @@
                     packageName,
                     AppOpsManager.MODE_IGNORED);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG,
+            Slog.w(LOG_TAG,
                     "Error while granting auto revoke exemption for " + packageName, e);
         }
     }
@@ -819,9 +829,7 @@
     }
 
     private void recordAssociation(Association association) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "recordAssociation(" + association + ")");
-        }
+        Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
         updateAssociations(associations -> CollectionUtils.add(associations, association));
     }
 
@@ -835,9 +843,7 @@
             final Set<Association> old = getAllAssociations(userId);
             Set<Association> associations = new ArraySet<>(old);
             associations = update.apply(associations);
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Updating associations: " + old + "  -->  " + associations);
-            }
+            Slog.i(LOG_TAG, "Updating associations: " + old + "  -->  " + associations);
             mCachedAssociations.put(userId, Collections.unmodifiableSet(associations));
             BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                     CompanionDeviceManagerService::persistAssociations,
@@ -866,9 +872,7 @@
     }
 
     private void persistAssociations(Set<Association> associations, int userId) {
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "Writing associations to disk: " + associations);
-        }
+        Slog.i(LOG_TAG, "Writing associations to disk: " + associations);
         final AtomicFile file = getStorageFileForUser(userId);
         synchronized (file) {
             file.write(out -> {
@@ -919,9 +923,7 @@
             if (mCachedAssociations.get(userId) == null) {
                 mCachedAssociations.put(userId, Collections.unmodifiableSet(
                         emptyIfNull(readAllAssociations(userId))));
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
-                }
+                Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
             }
             return mCachedAssociations.get(userId);
         }
@@ -1002,13 +1004,15 @@
     }
 
     void onDeviceConnected(String address) {
+        Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
+
         mCurrentlyConnectedDevices.add(address);
 
         for (UserInfo user : getAllUsers()) {
             for (Association association : getAllAssociations(user.id)) {
                 if (Objects.equals(address, association.getDeviceMacAddress())) {
                     if (association.getDeviceProfile() != null) {
-                        Log.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+                        Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
                                 + " to " + association.getPackageName()
                                 + " due to device connected: " + association.getDeviceMacAddress());
                         grantDeviceProfile(association);
@@ -1021,6 +1025,8 @@
     }
 
     private void grantDeviceProfile(Association association) {
+        Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
+
         if (association.getDeviceProfile() != null) {
             mRoleManager.addRoleHolderAsUser(
                     association.getDeviceProfile(),
@@ -1030,7 +1036,7 @@
                     getContext().getMainExecutor(),
                     success -> {
                         if (!success) {
-                            Log.e(LOG_TAG, "Failed to grant device profile role "
+                            Slog.e(LOG_TAG, "Failed to grant device profile role "
                                     + association.getDeviceProfile()
                                     + " to " + association.getPackageName()
                                     + " for user " + association.getUserId());
@@ -1040,6 +1046,8 @@
     }
 
     void onDeviceDisconnected(String address) {
+        Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
+
         mCurrentlyConnectedDevices.remove(address);
 
         onDeviceDisappeared(address);
@@ -1059,13 +1067,13 @@
         List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
                 info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
         if (packageResolveInfos.size() != 1) {
-            Log.w(LOG_TAG, "Device presence listener package must have exactly one "
+            Slog.w(LOG_TAG, "Device presence listener package must have exactly one "
                     + "CompanionDeviceService, but " + a.getPackageName()
                     + " has " + packageResolveInfos.size());
             return new ServiceConnector.NoOp<>();
         }
         ComponentName componentName = packageResolveInfos.get(0).serviceInfo.getComponentName();
-        Log.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
+        Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
         return new ServiceConnector.Impl<>(getContext(),
                 new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(componentName),
                 BIND_IMPORTANT,
@@ -1077,7 +1085,7 @@
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
             if (DEBUG) {
-                Log.i(LOG_TAG, "onScanResult(callbackType = "
+                Slog.i(LOG_TAG, "onScanResult(callbackType = "
                         + callbackType + ", result = " + result + ")");
             }
 
@@ -1096,9 +1104,9 @@
             if (errorCode == SCAN_FAILED_ALREADY_STARTED) {
                 // ignore - this might happen if BT tries to auto-restore scans for us in the
                 // future
-                Log.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED");
+                Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED");
             } else {
-                Log.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
+                Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
             }
         }
     }
@@ -1112,7 +1120,7 @@
         public void onReceive(Context context, Intent intent) {
             int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
             int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
-            Log.i(LOG_TAG, "Received BT state transition broadcast: "
+            Slog.d(LOG_TAG, "Received BT state transition broadcast: "
                     + BluetoothAdapter.nameForState(previousState)
                     + " -> " + BluetoothAdapter.nameForState(newState));
 
@@ -1122,7 +1130,7 @@
                 if (mBluetoothAdapter.getBluetoothLeScanner() != null) {
                     startBleScan();
                 } else {
-                    Log.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null");
+                    Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null");
                 }
             }
         }
@@ -1136,6 +1144,8 @@
 
         @Override
         public void run() {
+            Slog.i(LOG_TAG, "UnbindDeviceListenersRunnable.run(); devicesNearby = "
+                    + mDevicesLastNearby);
             int size = mDevicesLastNearby.size();
             for (int i = 0; i < size; i++) {
                 String address = mDevicesLastNearby.keyAt(i);
@@ -1162,12 +1172,15 @@
         }
 
         public void schedule() {
+            Slog.d(LOG_TAG,
+                    "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
             mMainHandler.removeCallbacks(this);
             mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
         }
 
         @Override
         public void run() {
+            Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")");
             onDeviceDisappeared(mAddress);
         }
     }
@@ -1187,6 +1200,8 @@
     }
 
     private void onDeviceNearby(String address) {
+        Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
+
         Date timestamp = new Date();
         Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
 
@@ -1203,7 +1218,7 @@
             for (Association association : getAllAssociations(address)) {
                 if (association.isNotifyOnDeviceNearby()) {
                     if (DEBUG) {
-                        Log.i(LOG_TAG, "Device " + address
+                        Slog.i(LOG_TAG, "Device " + address
                                 + " managed by " + association.getPackageName()
                                 + " is nearby on " + timestamp);
                     }
@@ -1215,11 +1230,13 @@
     }
 
     private void onDeviceDisappeared(String address) {
+        Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
+
         boolean hasDeviceListeners = false;
         for (Association association : getAllAssociations(address)) {
             if (association.isNotifyOnDeviceNearby()) {
                 if (DEBUG) {
-                    Log.i(LOG_TAG, "Device " + address
+                    Slog.i(LOG_TAG, "Device " + address
                             + " managed by " + association.getPackageName()
                             + " disappeared; last seen on " + mDevicesLastNearby.get(address));
                 }
@@ -1245,19 +1262,19 @@
     }
 
     private void initBleScanning() {
-        Log.i(LOG_TAG, "initBleScanning()");
+        Slog.i(LOG_TAG, "initBleScanning()");
 
         boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback(
                 new BluetoothAdapter.ServiceLifecycleCallback() {
                     @Override
                     public void onBluetoothServiceUp() {
-                        Log.i(LOG_TAG, "Bluetooth stack is up");
+                        Slog.i(LOG_TAG, "Bluetooth stack is up");
                         startBleScan();
                     }
 
                     @Override
                     public void onBluetoothServiceDown() {
-                        Log.w(LOG_TAG, "Bluetooth stack is down");
+                        Slog.w(LOG_TAG, "Bluetooth stack is down");
                     }
                 });
         if (bluetoothReady) {
@@ -1266,7 +1283,7 @@
     }
 
     void startBleScan() {
-        Log.i(LOG_TAG, "startBleScan()");
+        Slog.i(LOG_TAG, "startBleScan()");
 
         List<ScanFilter> filters = getBleScanFilters();
         if (filters.isEmpty()) {
@@ -1274,7 +1291,7 @@
         }
         BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
         if (scanner == null) {
-            Log.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)");
+            Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)");
         } else {
             scanner.startScan(
                     filters,
@@ -1321,7 +1338,7 @@
         try {
             return Long.parseLong(str);
         } catch (NumberFormatException e) {
-            Log.w(LOG_TAG, "Failed to parse", e);
+            Slog.w(LOG_TAG, "Failed to parse", e);
             return def;
         }
     }
@@ -1380,7 +1397,7 @@
                 }
                 return 0;
             } catch (Throwable t) {
-                Log.e(LOG_TAG, "Error running a command: $ " + cmd, t);
+                Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
                 getErrPrintWriter().println(Log.getStackTraceString(t));
                 return 1;
             }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 542a260..0725bb2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,10 +127,10 @@
         "android.hardware.health-V2.0-java",
         "android.hardware.health-V2.1-java",
         "android.hardware.light-V1-java",
-        "android.hardware.tv.cec-V1.0-java",
+        "android.hardware.tv.cec-V1.1-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",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4b9fed2..5077cc6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -44,7 +44,6 @@
 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;
@@ -1232,7 +1231,6 @@
     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;
@@ -1247,7 +1245,6 @@
             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 > TYPE_NONE) {
             netCap.addTransportType(transportType);
@@ -3587,10 +3584,10 @@
     }
 
     private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
-        handleRegisterNetworkRequest(Collections.singletonList(nri));
+        handleRegisterNetworkRequests(Collections.singleton(nri));
     }
 
-    private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) {
+    private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
         for (final NetworkRequestInfo nri : nris) {
             mNetworkRequestInfoLogs.log("REGISTER " + nri);
@@ -3721,7 +3718,15 @@
 
     private NetworkRequestInfo getNriForAppRequest(
             NetworkRequest request, int callingUid, String requestedOperation) {
-        final NetworkRequestInfo nri = mNetworkRequests.get(request);
+        // Looking up the app passed param request in mRequests isn't possible since it may return
+        // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to
+        // do the lookup since that will also find per-app default managed requests.
+        // Additionally, this lookup needs to be relatively fast (hence the lookup optimization)
+        // to avoid potential race conditions when validating a package->uid mapping when sending
+        // the callback on the very low-chance that an application shuts down prior to the callback
+        // being sent.
+        final NetworkRequestInfo nri = mNetworkRequests.get(request) != null
+                ? mNetworkRequests.get(request) : getNriForAppRequest(request);
 
         if (nri != null) {
             if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
@@ -3770,8 +3775,6 @@
         if (nri == null) {
             return;
         }
-        // handleReleaseNetworkRequest() paths don't apply to multilayer requests.
-        ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest");
         if (VDBG || (DBG && request.isRequest())) {
             log("releasing " + request + " (release request)");
         }
@@ -3783,7 +3786,6 @@
 
     private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
-
         nri.unlinkDeathRecipient();
         for (final NetworkRequest req : nri.mRequests) {
             mNetworkRequests.remove(req);
@@ -3806,6 +3808,16 @@
         cancelNpiRequests(nri);
     }
 
+    private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+        for (final NetworkRequestInfo nri : nris) {
+            if (mDefaultRequest == nri) {
+                // Make sure we never remove the default request.
+                continue;
+            }
+            handleRemoveNetworkRequest(nri);
+        }
+    }
+
     private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
         for (final NetworkRequest req : nri.mRequests) {
             cancelNpiRequest(req);
@@ -4976,12 +4988,12 @@
         }
     }
 
-    private void onUserAdded(int userId) {
-        mPermissionMonitor.onUserAdded(userId);
+    private void onUserAdded(UserHandle user) {
+        mPermissionMonitor.onUserAdded(user);
     }
 
-    private void onUserRemoved(int userId) {
-        mPermissionMonitor.onUserRemoved(userId);
+    private void onUserRemoved(UserHandle user) {
+        mPermissionMonitor.onUserRemoved(user);
     }
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -4989,15 +5001,18 @@
         public void onReceive(Context context, Intent intent) {
             ensureRunningOnConnectivityServiceThread();
             final String action = intent.getAction();
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
 
-            // UserId should be filled for below intents, check the existence.
-            if (userId == UserHandle.USER_NULL) return;
+            // User should be filled for below intents, check the existence.
+            if (user == null) {
+                Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
+                return;
+            }
 
             if (Intent.ACTION_USER_ADDED.equals(action)) {
-                onUserAdded(userId);
+                onUserAdded(user);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                onUserRemoved(userId);
+                onUserRemoved(user);
             }  else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
@@ -5110,6 +5125,21 @@
         final int mUid;
         @Nullable
         final String mCallingAttributionTag;
+        // In order to preserve the mapping of NetworkRequest-to-callback when apps register
+        // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
+        // maintained for keying off of. This is only a concern when the original nri
+        // mNetworkRequests changes which happens currently for apps that register callbacks to
+        // track the default network. In those cases, the nri is updated to have mNetworkRequests
+        // that match the per-app default nri that currently tracks the calling app's uid so that
+        // callbacks are fired at the appropriate time. When the callbacks fire,
+        // mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When
+        // callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue.
+        // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
+        @NonNull
+        private final NetworkRequest mNetworkRequestForCallback;
+        NetworkRequest getNetworkRequestForCallback() {
+            return mNetworkRequestForCallback;
+        }
 
         /**
          * Get the list of UIDs this nri applies to.
@@ -5125,13 +5155,15 @@
 
         NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
                 @Nullable String callingAttributionTag) {
-            this(Collections.singletonList(r), pi, callingAttributionTag);
+            this(Collections.singletonList(r), r, pi, callingAttributionTag);
         }
 
         NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
-                @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
+                @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
+                @Nullable String callingAttributionTag) {
+            ensureAllNetworkRequestsHaveType(r);
             mRequests = initializeRequests(r);
-            ensureAllNetworkRequestsHaveType(mRequests);
+            mNetworkRequestForCallback = requestForCallback;
             mPendingIntent = pi;
             mMessenger = null;
             mBinder = null;
@@ -5143,15 +5175,17 @@
 
         NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
                 @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
-            this(Collections.singletonList(r), m, binder, callingAttributionTag);
+            this(Collections.singletonList(r), r, m, binder, callingAttributionTag);
         }
 
-        NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m,
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+                @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
                 @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
             super();
+            ensureAllNetworkRequestsHaveType(r);
             mRequests = initializeRequests(r);
+            mNetworkRequestForCallback = requestForCallback;
             mMessenger = m;
-            ensureAllNetworkRequestsHaveType(mRequests);
             mBinder = binder;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
@@ -5166,12 +5200,26 @@
             }
         }
 
+        NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
+                @NonNull final List<NetworkRequest> r) {
+            super();
+            ensureAllNetworkRequestsHaveType(r);
+            mRequests = initializeRequests(r);
+            mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
+            mMessenger = nri.mMessenger;
+            mBinder = nri.mBinder;
+            mPid = nri.mPid;
+            mUid = nri.mUid;
+            mPendingIntent = nri.mPendingIntent;
+            mCallingAttributionTag = nri.mCallingAttributionTag;
+        }
+
         NetworkRequestInfo(@NonNull final NetworkRequest r) {
             this(Collections.singletonList(r));
         }
 
         NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
-            this(r, null /* pi */, null /* callingAttributionTag */);
+            this(r, r.get(0), null /* pi */, null /* callingAttributionTag */);
         }
 
         // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
@@ -5330,7 +5378,8 @@
                 // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
                 // 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);
+                networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
+                        defaultNc, callingUid, callingPackageName);
                 enforceAccessPermission();
                 break;
             case TRACK_SYSTEM_DEFAULT:
@@ -5369,10 +5418,10 @@
         }
         ensureValid(networkCapabilities);
 
-        NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+        final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
-        NetworkRequestInfo nri =
-                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
+        final NetworkRequestInfo nri = getNriToRegister(
+                networkRequest, messenger, binder, callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
         // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5394,6 +5443,30 @@
         return networkRequest;
     }
 
+    /**
+     * Return the nri to be used when registering a network request. Specifically, this is used with
+     * requests registered to track the default request. If there is currently a per-app default
+     * tracking the app requestor, then we need to create a version of this nri that mirrors that of
+     * the tracking per-app default so that callbacks are sent to the app requestor appropriately.
+     * @param nr the network request for the nri.
+     * @param msgr the messenger for the nri.
+     * @param binder the binder for the nri.
+     * @param callingAttributionTag the calling attribution tag for the nri.
+     * @return the nri to register.
+     */
+    private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr,
+            @Nullable final Messenger msgr, @Nullable final IBinder binder,
+            @Nullable String callingAttributionTag) {
+        final List<NetworkRequest> requests;
+        if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
+            requests = copyDefaultNetworkRequestsForUid(
+                    nr.getRequestorUid(), nr.getRequestorPackageName());
+        } else {
+            requests = Collections.singletonList(nr);
+        }
+        return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag);
+    }
+
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag) {
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
@@ -5687,6 +5760,102 @@
     }
 
     /**
+     * Return the default network request currently tracking the given uid.
+     * @param uid the uid to check.
+     * @return the NetworkRequestInfo tracking the given uid.
+     */
+    @NonNull
+    private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) {
+        for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+            if (nri == mDefaultRequest) {
+                continue;
+            }
+            // Checking the first request is sufficient as only multilayer requests will have more
+            // than one request and for multilayer, all requests will track the same uids.
+            if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) {
+                return nri;
+            }
+        }
+        return mDefaultRequest;
+    }
+
+    /**
+     * Get a copy of the network requests of the default request that is currently tracking the
+     * given uid.
+     * @param requestorUid the uid to check the default for.
+     * @param requestorPackageName the requestor's package name.
+     * @return a copy of the default's NetworkRequest that is tracking the given uid.
+     */
+    @NonNull
+    private List<NetworkRequest> copyDefaultNetworkRequestsForUid(
+            @NonNull final int requestorUid, @NonNull final String requestorPackageName) {
+        return copyNetworkRequestsForUid(
+                getDefaultRequestTrackingUid(requestorUid).mRequests,
+                requestorUid, requestorPackageName);
+    }
+
+    /**
+     * Copy the given nri's NetworkRequest collection.
+     * @param requestsToCopy the NetworkRequest collection to be copied.
+     * @param requestorUid the uid to set on the copied collection.
+     * @param requestorPackageName the package name to set on the copied collection.
+     * @return the copied NetworkRequest collection.
+     */
+    @NonNull
+    private List<NetworkRequest> copyNetworkRequestsForUid(
+            @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid,
+            @NonNull final String requestorPackageName) {
+        final List<NetworkRequest> requests = new ArrayList<>();
+        for (final NetworkRequest nr : requestsToCopy) {
+            requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid(
+                            nr.networkCapabilities, requestorUid, requestorPackageName),
+                    nr.legacyType, nextNetworkRequestId(), nr.type));
+        }
+        return requests;
+    }
+
+    @NonNull
+    private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
+            @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
+            @NonNull final String requestorPackageName) {
+        final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
+        netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
+        netCap.setSingleUid(requestorUid);
+        netCap.setUids(new ArraySet<>());
+        restrictRequestUidsForCallerAndSetRequestorInfo(
+                netCap, requestorUid, requestorPackageName);
+        return netCap;
+    }
+
+    /**
+     * Get the nri that is currently being tracked for callbacks by per-app defaults.
+     * @param nr the network request to check for equality against.
+     * @return the nri if one exists, null otherwise.
+     */
+    @Nullable
+    private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) {
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.getNetworkRequestForCallback().equals(nr)) {
+                return nri;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if an nri is currently being managed by per-app default networking.
+     * @param nri the nri to check.
+     * @return true if this nri is currently being managed by per-app default networking.
+     */
+    private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) {
+        // nri.mRequests.get(0) is only different from the original request filed in
+        // nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default
+        // functionality therefore if these two don't match, it means this particular nri is
+        // currently being managed by a per-app default.
+        return nri.getNetworkRequestForCallback() != nri.mRequests.get(0);
+    }
+
+    /**
      * Determine if an nri is a managed default request that disallows default networking.
      * @param nri the request to evaluate
      * @return true if device-default networking is disallowed
@@ -6764,13 +6933,9 @@
             return;
         }
         Bundle bundle = new Bundle();
-        // In the case of multi-layer NRIs, the first request is not necessarily the one that
-        // is satisfied. This is vexing, but the ConnectivityManager code that receives this
-        // callback is only using the request as a token to identify the callback, so it doesn't
-        // matter too much at this point as long as the callback can be found.
         // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
         // TODO: check if defensive copies of data is needed.
-        final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0));
+        final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
         putParcelable(bundle, nrForCallback);
         Message msg = Message.obtain();
         if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
@@ -8710,7 +8875,7 @@
         if (DBG) {
             log("set OEM network preferences :" + preference.toString());
         }
-        final List<NetworkRequestInfo> nris =
+        final ArraySet<NetworkRequestInfo> nris =
                 new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
         updateDefaultNetworksForOemNetworkPreference(nris);
         mOemNetworkPreferences = preference;
@@ -8722,27 +8887,88 @@
     }
 
     private void updateDefaultNetworksForOemNetworkPreference(
-            @NonNull final List<NetworkRequestInfo> nris) {
-        ensureRunningOnConnectivityServiceThread();
-        clearNonDefaultNetworkAgents();
-        addDefaultNetworkRequests(nris);
+            @NonNull final Set<NetworkRequestInfo> nris) {
+        handleRemoveNetworkRequests(mDefaultNetworkRequests);
+        addPerAppDefaultNetworkRequests(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]);
+    private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+        ensureRunningOnConnectivityServiceThread();
+        mDefaultNetworkRequests.addAll(nris);
+        final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
+                getPerAppCallbackRequestsToUpdate();
+        handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+        final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
+        nrisToRegister.addAll(
+                createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
+        handleRegisterNetworkRequests(nrisToRegister);
+    }
+
+    /**
+     * All current requests that are tracking the default network need to be assessed as to whether
+     * or not the current set of per-application default requests will be changing their default
+     * network. If so, those requests will need to be updated so that they will send callbacks for
+     * default network changes at the appropriate time. Additionally, those requests tracking the
+     * default that were previously updated by this flow will need to be reassessed.
+     * @return the nris which will need to be updated.
+     */
+    private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() {
+        final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>();
+        // Get the distinct nris to check since for multilayer requests, it is possible to have the
+        // same nri in the map's values for each of its NetworkRequest objects.
+        final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values());
         for (final NetworkRequestInfo nri : nris) {
-            if (mDefaultRequest != nri) {
-                handleRemoveNetworkRequest(nri);
+            // Include this nri if it is currently being tracked.
+            if (isPerAppTrackedNri(nri)) {
+                defaultCallbackRequests.add(nri);
+                continue;
+            }
+            // We only track callbacks for requests tracking the default.
+            if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) {
+                continue;
+            }
+            // Include this nri if it will be tracked by the new per-app default requests.
+            final boolean isNriGoingToBeTracked =
+                    getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest;
+            if (isNriGoingToBeTracked) {
+                defaultCallbackRequests.add(nri);
             }
         }
+        return defaultCallbackRequests;
     }
 
-    private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) {
-        mDefaultNetworkRequests.addAll(nris);
-        handleRegisterNetworkRequest(nris);
+    /**
+     * Create nris for those network requests that are currently tracking the default network that
+     * are being controlled by a per-application default.
+     * @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the
+     * foundation when creating the nri. Important items include the calling uid's original
+     * NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These
+     * requests are assumed to have already been validated as needing to be updated.
+     * @return the Set of nris to use when registering network requests.
+     */
+    private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister(
+            @NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) {
+        final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
+        for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
+            final NetworkRequestInfo trackingNri =
+                    getDefaultRequestTrackingUid(callbackRequest.mUid);
+
+            // If this nri is not being tracked, the change it back to an untracked nri.
+            if (trackingNri == mDefaultRequest) {
+                callbackRequestsToRegister.add(new NetworkRequestInfo(
+                        callbackRequest,
+                        Collections.singletonList(callbackRequest.getNetworkRequestForCallback())));
+                continue;
+            }
+
+            final String requestorPackageName =
+                    callbackRequest.mRequests.get(0).getRequestorPackageName();
+            callbackRequestsToRegister.add(new NetworkRequestInfo(
+                    callbackRequest,
+                    copyNetworkRequestsForUid(
+                            trackingNri.mRequests, callbackRequest.mUid, requestorPackageName)));
+        }
+        return callbackRequestsToRegister;
     }
 
     /**
@@ -8750,9 +8976,9 @@
      */
     @VisibleForTesting
     final class OemNetworkRequestFactory {
-        List<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+        ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
                 @NonNull final OemNetworkPreferences preference) {
-            final List<NetworkRequestInfo> nris = new ArrayList<>();
+            final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
             final SparseArray<Set<Integer>> uids =
                     createUidsFromOemNetworkPreferences(preference);
             for (int i = 0; i < uids.size(); i++) {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c6a8660..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, 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/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 0aee780..edaf6a90 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -105,8 +105,6 @@
 
     private static final String TAG = "SensorPrivacyService";
 
-    private static final int SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000;
-
     /** Version number indicating compatibility parsing the persisted file */
     private static final int CURRENT_PERSISTENCE_VERSION = 1;
     /** Version number indicating the persisted data needs upgraded to match new internal data
@@ -568,7 +566,7 @@
                                     // User may no longer exist or isn't set
                                     continue;
                                 }
-                                int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR);
+                                int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
                                 boolean isEnabled = parser.getAttributeBoolean(null,
                                         XML_ATTRIBUTE_ENABLED);
                                 SparseBooleanArray userIndividualEnabled = individualEnabled.get(
@@ -756,10 +754,7 @@
 
                     suppressPackageReminderTokens.add(token);
                 } else {
-                    mHandler.postDelayed(PooledLambda.obtainRunnable(
-                            SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
-                            this, key, token),
-                            SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS);
+                    mHandler.removeSuppressPackageReminderToken(key, token);
                 }
             }
         }
@@ -1110,6 +1105,13 @@
             }
             listeners.finishBroadcast();
         }
+
+        public void removeSuppressPackageReminderToken(Pair<String, UserHandle> key,
+                IBinder token) {
+            sendMessage(PooledLambda.obtainMessage(
+                    SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
+                    mSensorPrivacyServiceImpl, key, token));
+        }
     }
 
     private final class DeathRecipient implements IBinder.DeathRecipient {
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 27210da..329ab99 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -22,6 +22,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -29,8 +30,10 @@
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnManagementService;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.net.wifi.WifiInfo;
 import android.os.Binder;
@@ -54,6 +57,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.LocationPermissionChecker;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -124,6 +128,7 @@
  *
  * @hide
  */
+// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
 public class VcnManagementService extends IVcnManagementService.Stub {
     @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
 
@@ -147,6 +152,9 @@
     @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
     @NonNull private final VcnContext mVcnContext;
 
+    /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */
+    @Nullable private LocationPermissionChecker mLocationPermissionChecker;
+
     @GuardedBy("mLock")
     @NonNull
     private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
@@ -169,6 +177,10 @@
     private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
         mContext = requireNonNull(context, "Missing context");
@@ -293,8 +305,8 @@
                 @NonNull ParcelUuid subscriptionGroup,
                 @NonNull VcnConfig config,
                 @NonNull TelephonySubscriptionSnapshot snapshot,
-                @NonNull VcnSafemodeCallback safemodeCallback) {
-            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
+                @NonNull VcnCallback vcnCallback) {
+            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
         }
 
         /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -302,6 +314,11 @@
             // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
+
+        /** Creates a new LocationPermissionChecker for the provided Context. */
+        public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
+            return new LocationPermissionChecker(context);
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -309,6 +326,7 @@
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkProvider(mNetworkProvider);
         mTelephonySubscriptionTracker.register();
+        mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext());
     }
 
     private void enforcePrimaryUser() {
@@ -440,12 +458,10 @@
         // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
         //                    VCN.
 
-        final VcnSafemodeCallbackImpl safemodeCallback =
-                new VcnSafemodeCallbackImpl(subscriptionGroup);
+        final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
 
         final Vcn newInstance =
-                mDeps.newVcn(
-                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
+                mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
         mVcns.put(subscriptionGroup, newInstance);
 
         // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -551,6 +567,14 @@
         }
     }
 
+    /** Get current VcnStatusCallbacks for testing purposes. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
+        }
+    }
+
     /** Binder death recipient used to remove a registered policy listener. */
     private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
         @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
@@ -672,22 +696,136 @@
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
 
-    /** Callback for signalling when a Vcn has entered Safemode. */
-    public interface VcnSafemodeCallback {
-        /** Called by a Vcn to signal that it has entered Safemode. */
-        void onEnteredSafemode();
-    }
+    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
+        @NonNull final ParcelUuid mSubGroup;
+        @NonNull final IVcnStatusCallback mCallback;
+        @NonNull final String mPkgName;
+        final int mUid;
 
-    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
-    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
-        @NonNull private final ParcelUuid mSubGroup;
-
-        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
-            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        private VcnStatusCallbackInfo(
+                @NonNull ParcelUuid subGroup,
+                @NonNull IVcnStatusCallback callback,
+                @NonNull String pkgName,
+                int uid) {
+            mSubGroup = subGroup;
+            mCallback = callback;
+            mPkgName = pkgName;
+            mUid = uid;
         }
 
         @Override
-        public void onEnteredSafemode() {
+        public void binderDied() {
+            Log.e(TAG, "app died without unregistering VcnStatusCallback");
+            unregisterVcnStatusCallback(mCallback);
+        }
+    }
+
+    /** Registers the provided callback for receiving VCN status updates. */
+    @Override
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subGroup,
+            @NonNull IVcnStatusCallback callback,
+            @NonNull String opPkgName) {
+        final int callingUid = mDeps.getBinderCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(subGroup, "subGroup must not be null");
+            requireNonNull(callback, "callback must not be null");
+            requireNonNull(opPkgName, "opPkgName must not be null");
+
+            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
+
+            final IBinder cbBinder = callback.asBinder();
+            final VcnStatusCallbackInfo cbInfo =
+                    new VcnStatusCallbackInfo(
+                            subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+
+            try {
+                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
+            } catch (RemoteException e) {
+                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
+                return;
+            }
+
+            synchronized (mLock) {
+                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
+                    throw new IllegalStateException(
+                            "Attempting to register a callback that is already in use");
+                }
+
+                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /** Unregisters the provided callback from receiving future VCN status updates. */
+    @Override
+    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(callback, "callback must not be null");
+
+            final IBinder cbBinder = callback.asBinder();
+            synchronized (mLock) {
+                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
+
+                if (cbInfo != null) {
+                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
+    /** Callback for Vcn signals sent up to VcnManagementService. */
+    public interface VcnCallback {
+        /** Called by a Vcn to signal that it has entered safe mode. */
+        void onEnteredSafeMode();
+
+        /** Called by a Vcn to signal that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
+    }
+
+    /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
+    private class VcnCallbackImpl implements VcnCallback {
+        @NonNull private final ParcelUuid mSubGroup;
+
+        private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
+            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        }
+
+        private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) {
+            if (!mSubGroup.equals(cbInfo.mSubGroup)) {
+                return false;
+            }
+
+            if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
+                    mSubGroup, cbInfo.mPkgName)) {
+                return false;
+            }
+
+            if (!mLocationPermissionChecker.checkLocationPermission(
+                    cbInfo.mPkgName,
+                    "VcnStatusCallback" /* featureId */,
+                    cbInfo.mUid,
+                    null /* message */)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void onEnteredSafeMode() {
             synchronized (mLock) {
                 // Ignore if this subscription group doesn't exist anymore
                 if (!mVcns.containsKey(mSubGroup)) {
@@ -695,6 +833,40 @@
                 }
 
                 notifyAllPolicyListenersLocked();
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (isCallbackPermissioned(cbInfo)) {
+                        Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            synchronized (mLock) {
+                // Ignore if this subscription group doesn't exist anymore
+                if (!mVcns.containsKey(mSubGroup)) {
+                    return;
+                }
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (isCallbackPermissioned(cbInfo)) {
+                        Binder.withCleanCallingIdentity(
+                                () ->
+                                        cbInfo.mCallback.onGatewayConnectionError(
+                                                networkCapabilities,
+                                                errorCode,
+                                                exceptionClass,
+                                                exceptionMessage));
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bedd19b..5ee0e04 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -130,6 +130,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_SHORT_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_STARTER_CMD;
+import static com.android.server.wm.ActivityTaskManagerService.DUMP_TOP_RESUMED_ACTIVITY;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
 
@@ -186,6 +187,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;
@@ -352,6 +354,7 @@
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.graphics.fonts.FontManagerInternal;
 import com.android.server.job.JobSchedulerInternal;
+import com.android.server.os.NativeTombstoneManager;
 import com.android.server.pm.Installer;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.uri.GrantUri;
@@ -5626,6 +5629,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.
@@ -8255,6 +8275,9 @@
         mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
                 "getHistoricalProcessExitReasons", null);
 
+        NativeTombstoneManager tombstoneService = LocalServices.getService(
+                NativeTombstoneManager.class);
+
         final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>();
         if (!TextUtils.isEmpty(packageName)) {
             final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
@@ -8262,11 +8285,13 @@
             if (uid != Process.INVALID_UID) {
                 mProcessList.mAppExitInfoTracker.getExitInfo(
                         packageName, uid, pid, maxNum, results);
+                tombstoneService.collectTombstones(results, uid, pid, maxNum);
             }
         } else {
             // If no package name is given, use the caller's uid as the filter uid.
             mProcessList.mAppExitInfoTracker.getExitInfo(
                     packageName, callingUid, pid, maxNum, results);
+            tombstoneService.collectTombstones(results, callingUid, pid, maxNum);
         }
 
         return new ParceledListSlice<ApplicationExitInfo>(results);
@@ -8650,7 +8675,8 @@
             if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)
                     || DUMP_LASTANR_CMD.equals(cmd) || DUMP_LASTANR_TRACES_CMD.equals(cmd)
                     || DUMP_STARTER_CMD.equals(cmd) || DUMP_CONTAINERS_CMD.equals(cmd)
-                    || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)) {
+                    || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)
+                    || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
                 mAtmInternal.dump(
                         cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);
             } else if ("binder-proxies".equals(cmd)) {
@@ -10747,6 +10773,15 @@
                     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: ");
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 17be210..b85d729 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -63,8 +63,10 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemServiceManager;
+import com.android.server.os.NativeTombstoneManager;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -78,6 +80,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
@@ -762,6 +765,10 @@
      * Helper function for shell command
      */
     void clearHistoryProcessExitInfo(String packageName, int userId) {
+        NativeTombstoneManager tombstoneService = LocalServices.getService(
+                NativeTombstoneManager.class);
+        Optional<Integer> appId = Optional.empty();
+
         if (TextUtils.isEmpty(packageName)) {
             synchronized (mLock) {
                 removeByUserIdLocked(userId);
@@ -769,10 +776,13 @@
         } else {
             final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
                     PackageManager.MATCH_ALL, userId);
+            appId = Optional.of(UserHandle.getAppId(uid));
             synchronized (mLock) {
                 removePackageLocked(packageName, uid, true, userId);
             }
         }
+
+        tombstoneService.purge(Optional.of(userId), appId);
         schedulePersistProcessExitInfo(true);
     }
 
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index fc7a476..c8630fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1536,6 +1536,14 @@
                 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) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 52bb55f..fc28bfb 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -50,8 +50,6 @@
 
 import libcore.util.EmptyArray;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -149,7 +147,6 @@
      * 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 SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
@@ -209,11 +206,22 @@
                         = 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);
+                    try {
+                        final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData().get(
+                                EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+                        // 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);
+                    } catch (TimeoutException | InterruptedException e) {
+                        Slog.w(TAG, "timeout or interrupt reading initial getEnergyConsumedAsync: "
+                                + e);
+                        // Continue running, later attempts to query may be successful.
+                    } catch (ExecutionException e) {
+                        Slog.wtf(TAG, "exception reading initial getEnergyConsumedAsync: "
+                                + e.getCause());
+                        // Continue running, later attempts to query may be successful.
+                    }
                     numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
                     supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
@@ -498,6 +506,8 @@
         CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null);
         boolean railUpdated = false;
 
+        CompletableFuture<EnergyConsumerResult[]> futureECRs = getMeasuredEnergyLocked(updateFlags);
+
         if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
             // We were asked to fetch WiFi data.
             // Only fetch WiFi power data if it is supported.
@@ -574,9 +584,23 @@
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
 
-        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
-                mMeasuredEnergySnapshot == null ? null :
-                mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas;
+        if (mMeasuredEnergySnapshot == null || futureECRs == null) {
+            measuredEnergyDeltas = null;
+        } else {
+            EnergyConsumerResult[] ecrs;
+            try {
+                ecrs = futureECRs.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException | InterruptedException e) {
+                // TODO (b/180519623): Invalidate the MeasuredEnergy derived data until next reset.
+                Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync: " + e);
+                ecrs = null;
+            } catch (ExecutionException e) {
+                Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: " + e.getCause());
+                ecrs = null;
+            }
+            measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs);
+        }
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
@@ -786,22 +810,29 @@
         return buckets;
     }
 
-    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
+    /** Get all {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
-        try {
-            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;
-        }
+    @Nullable
+    private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData() {
+        return getEnergyConsumptionData(new int[0]);
+    }
+
+    /**
+     * Get {@link EnergyConsumerResult}s of the specified {@link EnergyConsumer} ids with the latest
+     * energy usage since boot.
+     */
+    @GuardedBy("mWorkerLock")
+    @Nullable
+    private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData(int[] consumerIds) {
+        return mPowerStatsInternal.getEnergyConsumedAsync(consumerIds);
     }
 
     /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
+    @VisibleForTesting
     @GuardedBy("mWorkerLock")
-    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
-    {
+    @Nullable
+    public CompletableFuture<EnergyConsumerResult[]> getMeasuredEnergyLocked(
+            @ExternalUpdateFlag int flags) {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -809,24 +840,27 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energyConsumerIds = new ArrayList<>();
+        final IntArray energyConsumerIds = new IntArray();
+        if ((flags & UPDATE_CPU) != 0) {
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER);
+        }
         if ((flags & UPDATE_DISPLAY) != 0) {
             addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
 
-        if (energyConsumerIds.isEmpty()) {
+        if (energyConsumerIds.size() == 0) {
             return null;
         }
-        // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
-        return getEnergyConsumptionData();
+        return getEnergyConsumptionData(energyConsumerIds.toArray());
     }
 
     @GuardedBy("mWorkerLock")
     private void addEnergyConsumerIdLocked(
-            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
-        final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
-        energyConsumerIds.add(consumerId);
+            IntArray energyConsumerIds, @EnergyConsumerType int type) {
+        final int[] consumerIds = mEnergyConsumerTypeToIdMap.get(type);
+        if (consumerIds == null) return;
+        energyConsumerIds.addAll(consumerIds);
     }
 
     /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
@@ -840,12 +874,10 @@
             return null;
         }
 
-        // 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);
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        final SparseArray<IntArray> tempTypeToId = new SparseArray<>();
 
         // Add all expected EnergyConsumers to the maps
         for (final EnergyConsumer consumer : energyConsumers) {
@@ -862,9 +894,23 @@
                 }
             }
             idToConsumer.put(consumer.id, consumer);
-            // TODO(b/180029015): Also populate typeToIds map
+
+            IntArray ids = tempTypeToId.get(consumer.type);
+            if (ids == null) {
+                ids = new IntArray();
+                tempTypeToId.put(consumer.type, ids);
+            }
+            ids.add(consumer.id);
         }
-        // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+
+        mEnergyConsumerTypeToIdMap = new SparseArray<>(tempTypeToId.size());
+        // Populate mEnergyConsumerTypeToIdMap with EnergyConsumer type to ids mappings
+        final int size = tempTypeToId.size();
+        for (int i = 0; i < size; i++) {
+            final int consumerType = tempTypeToId.keyAt(i);
+            final int[] consumerIds = tempTypeToId.valueAt(i).toArray();
+            mEnergyConsumerTypeToIdMap.put(consumerType, consumerIds);
+        }
         return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 76c3467..26ce0d7 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -61,7 +61,7 @@
     private final Object mLock = new Object();
     private final Handler mHandler;
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, Settings> mSettings = new ArrayMap<>();
+    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
 
     public GameManagerService(Context context) {
         this(context, createServiceThread().getLooper());
@@ -99,7 +99,7 @@
                     synchronized (mLock) {
                         removeMessages(WRITE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
-                            Settings userSettings = mSettings.get(userId);
+                            GameManagerSettings userSettings = mSettings.get(userId);
                             userSettings.writePersistentDataLocked();
                         }
                     }
@@ -123,7 +123,7 @@
                         removeMessages(WRITE_SETTINGS, msg.obj);
                         removeMessages(REMOVE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
-                            final Settings userSettings = mSettings.get(userId);
+                            final GameManagerSettings userSettings = mSettings.get(userId);
                             mSettings.remove(userId);
                             userSettings.writePersistentDataLocked();
                         }
@@ -190,7 +190,7 @@
             if (!mSettings.containsKey(userId)) {
                 return GameManager.GAME_MODE_UNSUPPORTED;
             }
-            Settings userSettings = mSettings.get(userId);
+            GameManagerSettings userSettings = mSettings.get(userId);
             return userSettings.getGameModeLocked(packageName);
         }
     }
@@ -211,7 +211,7 @@
             if (!mSettings.containsKey(userId)) {
                 return;
             }
-            Settings userSettings = mSettings.get(userId);
+            GameManagerSettings userSettings = mSettings.get(userId);
             userSettings.setGameModeLocked(packageName, gameMode);
             final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
             msg.obj = userId;
@@ -235,7 +235,8 @@
                 return;
             }
 
-            Settings userSettings = new Settings(Environment.getDataSystemDeDirectory(userId));
+            GameManagerSettings userSettings =
+                    new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
             mSettings.put(userId, userSettings);
             userSettings.readPersistentDataLocked();
         }
diff --git a/services/core/java/com/android/server/app/Settings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
similarity index 98%
rename from services/core/java/com/android/server/app/Settings.java
rename to services/core/java/com/android/server/app/GameManagerSettings.java
index ab367fb..3e32380 100644
--- a/services/core/java/com/android/server/app/Settings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -41,7 +41,7 @@
  * Persists all GameService related settings.
  * @hide
  */
-public class Settings {
+public class GameManagerSettings {
 
     // The XML file follows the below format:
     // <?xml>
@@ -63,7 +63,7 @@
     // PackageName -> GameMode
     private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
 
-    Settings(File dataDir) {
+    GameManagerSettings(File dataDir) {
         mSystemDir = new File(dataDir, "system");
         mSystemDir.mkdirs();
         FileUtils.setPermissions(mSystemDir.toString(),
@@ -144,6 +144,7 @@
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
                     && type != XmlPullParser.END_DOCUMENT) {
+                // Do nothing
             }
             if (type != XmlPullParser.START_TAG) {
                 Slog.wtf(GameManagerService.TAG,
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/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b15a886..e19745e 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -40,6 +40,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceService;
@@ -144,13 +145,14 @@
 
     private final class AuthServiceImpl extends IAuthService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName)
-                throws RemoteException {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) throws RemoteException {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                return mInjector.getBiometricService().createTestSession(sensorId, opPackageName);
+                return mInjector.getBiometricService()
+                        .createTestSession(sensorId, callback, opPackageName);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 614c5f1..00a4e43 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -44,6 +44,7 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
@@ -570,13 +571,13 @@
      */
     private final class BiometricServiceWrapper extends IBiometricService.Stub {
         @Override // Binder call
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName)
-                throws RemoteException {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) throws RemoteException {
             checkInternalPermission();
 
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
-                    return sensor.impl.createTestSession(opPackageName);
+                    return sensor.impl.createTestSession(callback, opPackageName);
                 }
             }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index e062695..16f82af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -37,18 +37,16 @@
 
     private static final String TAG = "Biometrics/RemovalClient";
 
-    protected final int mBiometricId;
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
 
     public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+            int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
+            @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 statsModality, BiometricsProtoEnums.ACTION_REMOVE,
                 BiometricsProtoEnums.CLIENT_UNKNOWN);
-        mBiometricId = biometricId;
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -68,6 +66,7 @@
 
     @Override
     public void onRemoved(@Nullable BiometricAuthenticator.Identifier identifier, int remaining) {
+        Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
         if (identifier != null) {
             mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
                     identifier.getBiometricId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index f37cf18..06b049b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.IFaceService;
 import android.os.IBinder;
@@ -41,8 +42,9 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
-        return mFaceService.createTestSession(mSensorId, opPackageName);
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
+        return mFaceService.createTestSession(mSensorId, callback, opPackageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 8253927..6dbd590 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -31,6 +31,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
@@ -133,7 +134,8 @@
      */
     private final class FaceServiceWrapper extends IFaceService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -143,7 +145,7 @@
                 return null;
             }
 
-            return provider.createTestSession(sensorId, opPackageName);
+            return provider.createTestSession(sensorId, callback, opPackageName);
         }
 
         @Override
@@ -386,7 +388,22 @@
                     opPackageName);
         }
 
-        @Override
+        @Override // Binder call
+        public void removeAll(final IBinder token, final int userId,
+                final IFaceServiceReceiver receiver, final String opPackageName) {
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+
+            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            if (provider == null) {
+                Slog.w(TAG, "Null provider for removeAll");
+                return;
+            }
+
+            provider.second.scheduleRemoveAll(provider.first, token, userId, receiver,
+                    opPackageName);
+        }
+
+        @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index cc24b89..88edfbf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -28,6 +29,7 @@
 import android.os.NativeHandle;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -109,6 +111,9 @@
     void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
 
+    void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
+
     void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken);
 
     void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@@ -120,7 +125,8 @@
 
     void startPreparedClient(int sensorId, int cookie);
 
-    void scheduleInternalCleanup(int sensorId, int userId);
+    void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback);
 
     void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer);
@@ -130,7 +136,8 @@
     void dumpInternal(int sensorId, @NonNull PrintWriter pw);
 
     @NonNull
-    ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
+    ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName);
 
     void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args);
 }
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 897ebd7..a5e6ddb 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
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
 import android.hardware.face.Face;
@@ -28,10 +29,12 @@
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.HashSet;
@@ -49,6 +52,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final FaceProvider mProvider;
     @NonNull private final Sensor mSensor;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -132,9 +136,11 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback,
             @NonNull FaceProvider provider, @NonNull Sensor sensor) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mProvider = provider;
         mSensor = sensor;
         mEnrollmentIds = new HashSet<>();
@@ -224,6 +230,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mProvider.scheduleInternalCleanup(mSensorId, userId);
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index 9680e4e..c6696aed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -61,7 +61,7 @@
         // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         return new FaceRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
+                utils, sensorId, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1b6b9d7..1d8f210 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -25,6 +25,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.Face;
@@ -177,7 +178,8 @@
         for (int i = 0; i < mSensors.size(); i++) {
             final int sensorId = mSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
-            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                    null /* callback */);
         }
 
         return mDaemon;
@@ -468,6 +470,25 @@
     @Override
     public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        scheduleRemoveSpecifiedIds(sensorId, token, new int[] {faceId}, userId, receiver,
+                opPackageName);
+    }
+
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final List<Face> faces = FaceUtils.getInstance(sensorId)
+                .getBiometricsForUser(mContext, userId);
+        final int[] faceIds = new int[faces.size()];
+        for (int i = 0; i < faces.size(); i++) {
+            faceIds[i] = faces.get(i).getBiometricId();
+        }
+
+        scheduleRemoveSpecifiedIds(sensorId, token, faceIds, userId, receiver, opPackageName);
+    }
+
+    private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
+            int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
             final IFace daemon = getHalInstance();
             if (daemon == null) {
@@ -485,7 +506,7 @@
 
                 final FaceRemovalClient client = new FaceRemovalClient(mContext,
                         mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), faceId, userId,
+                        new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                         opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
 
@@ -543,7 +564,8 @@
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             final IFace daemon = getHalInstance();
             if (daemon == null) {
@@ -564,7 +586,7 @@
                                 FaceUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
 
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
@@ -627,8 +649,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession();
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return mSensors.get(sensorId).createTestSession(callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 1cb5031..48796c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -38,19 +38,22 @@
 class FaceRemovalClient extends RemovalClient<Face, ISession> {
     private static final String TAG = "FaceRemovalClient";
 
+    final int[] mBiometricIds;
+
     FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
-            int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+            int[] biometricIds, int userId, @NonNull String owner,
+            @NonNull BiometricUtils<Face> utils, int sensorId,
+            @NonNull Map<Integer, Long> authenticatorIds) {
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+        mBiometricIds = biometricIds;
     }
 
     @Override
     protected void startHalOperation() {
         try {
-            final int[] ids = new int[]{mBiometricId};
-            getFreshDaemon().removeEnrollments(mSequentialId, ids);
+            getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
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 4925ce0..3434acb 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
@@ -22,6 +22,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.EnrollmentFrame;
 import android.hardware.biometrics.face.Error;
@@ -459,8 +460,9 @@
         }
     }
 
-    @NonNull ITestSession createTestSession() {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+    @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
+                mProvider, this);
     }
 
     void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
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 d519d60..e8668ed 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
@@ -21,14 +21,17 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 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.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -43,6 +46,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final Face10 mFace10;
     @NonNull private final Face10.HalResultController mHalResultController;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -120,10 +124,12 @@
         }
     };
 
-    BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10,
+    BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback, @NonNull Face10 face10,
             @NonNull Face10.HalResultController halResultController) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mFace10 = face10;
         mHalResultController = halResultController;
         mEnrollmentIds = new HashSet<>();
@@ -201,6 +207,25 @@
     public void cleanupInternalState(int userId) {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFace10.scheduleInternalCleanup(mSensorId, userId);
+        mFace10.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index e46661a..ee8823e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.face.Face;
@@ -123,7 +124,7 @@
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
         public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId);
+            scheduleInternalCleanup(newUserId, null /* callback */);
             scheduleGetFeature(mSensorId, new Binder(), newUserId,
                     BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
                     null, mContext.getOpPackageName());
@@ -437,7 +438,7 @@
         Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
         if (halId != 0) {
             scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
             scheduleGetFeature(mSensorId, new Binder(),
                     ActivityManager.getCurrentUser(),
                     BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
@@ -671,6 +672,20 @@
         });
     }
 
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            // For IBiometricsFace@1.0, remove(0) means remove all enrollments
+            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+                    new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
+                    opPackageName,
+                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
 
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
@@ -742,7 +757,8 @@
         });
     }
 
-    private void scheduleInternalCleanup(int userId) {
+    private void scheduleInternalCleanup(int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -750,13 +766,14 @@
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, callback);
         });
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
-        scheduleInternalCleanup(userId);
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
+        scheduleInternalCleanup(userId, callback);
     }
 
     @Override
@@ -930,7 +947,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorId, this, mHalResultController);
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return new BiometricTestSessionImpl(mContext, mSensorId, callback, this,
+                mHalResultController);
     }
 }
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..3ae2011 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,17 +33,20 @@
 
 /**
  * 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";
 
+    private final int mBiometricId;
+
     FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
             int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+        mBiometricId = biometricId;
     }
 
     @Override
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 13bd1c2..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,11 +18,11 @@
 
 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;
@@ -129,15 +129,4 @@
         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/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 34a9099..32e9409 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintService;
 import android.os.IBinder;
@@ -42,8 +43,9 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
-        return mFingerprintService.createTestSession(mSensorId, opPackageName);
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
+        return mFingerprintService.createTestSession(mSensorId, callback, opPackageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index b0e42cd..396dd5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -43,6 +43,7 @@
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
@@ -109,7 +110,8 @@
      */
     private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
         @Override
-        public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+                @NonNull String opPackageName) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -119,7 +121,7 @@
                 return null;
             }
 
-            return provider.createTestSession(sensorId, opPackageName);
+            return provider.createTestSession(sensorId, callback, opPackageName);
         }
 
         @Override
@@ -499,7 +501,21 @@
                     opPackageName);
         }
 
-        @Override
+        @Override // Binder call
+        public void removeAll(final IBinder token, final int userId,
+                final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+
+            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            if (provider == null) {
+                Slog.w(TAG, "Null provider for removeAll");
+                return;
+            }
+            provider.second.scheduleRemoveAll(provider.first, token, receiver, userId,
+                    opPackageName);
+        }
+
+        @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index f672ae5..dfec2e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -28,6 +29,7 @@
 import android.os.IBinder;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -98,7 +100,12 @@
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
             @NonNull String opPackageName);
 
-    void scheduleInternalCleanup(int sensorId, int userId);
+    void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName);
+
+    void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback);
 
     boolean isHardwareDetected(int sensorId);
 
@@ -133,5 +140,6 @@
     void dumpInternal(int sensorId, @NonNull PrintWriter pw);
 
     @NonNull
-    ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
+    ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index ea9c709..20b3254 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -21,14 +21,17 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import java.util.HashSet;
@@ -46,6 +49,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final FingerprintProvider mProvider;
     @NonNull private final Sensor mSensor;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -110,9 +114,11 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
-            @NonNull FingerprintProvider provider, @NonNull Sensor sensor) {
+            @NonNull ITestSessionCallback callback, @NonNull FingerprintProvider provider,
+            @NonNull Sensor sensor) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mProvider = provider;
         mSensor = sensor;
         mEnrollmentIds = new HashSet<>();
@@ -192,6 +198,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mProvider.scheduleInternalCleanup(mSensorId, userId);
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 2a0e984..0de3f4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -60,7 +60,7 @@
             String owner, BiometricUtils<Fingerprint> utils, int sensorId,
             Map<Integer, Long> authenticatorIds) {
         return new FingerprintRemovalClient(context, lazyDaemon, token,
-                null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
-                sensorId, authenticatorIds);
+                null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
+                utils, sensorId, authenticatorIds);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0bd2f24..598cc89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -25,6 +25,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
@@ -185,7 +186,8 @@
         for (int i = 0; i < mSensors.size(); i++) {
             final int sensorId = mSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
-            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+                    null /* callback */);
         }
 
         return mDaemon;
@@ -490,6 +492,27 @@
     public void scheduleRemove(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
             @NonNull String opPackageName) {
+        scheduleRemoveSpecifiedIds(sensorId, token, new int[] {fingerId}, userId, receiver,
+                opPackageName);
+    }
+
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName) {
+        final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId)
+                .getBiometricsForUser(mContext, userId);
+        final int[] fingerIds = new int[fingers.size()];
+        for (int i = 0; i < fingers.size(); i++) {
+            fingerIds[i] = fingers.get(i).getBiometricId();
+        }
+
+        scheduleRemoveSpecifiedIds(sensorId, token, fingerIds, userId, receiver, opPackageName);
+    }
+
+    private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token,
+            int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver,
+            @NonNull String opPackageName) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -507,7 +530,7 @@
 
                 final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
                         mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), fingerId, userId,
+                        new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                         opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
                 mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
@@ -518,7 +541,8 @@
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -538,7 +562,7 @@
                                 mContext.getOpPackageName(), sensorId, enrolledList,
                                 FingerprintUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
@@ -683,8 +707,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession();
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return mSensors.get(sensorId).createTestSession(callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index 4a99a7b..c622208 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -39,20 +39,22 @@
 class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> {
     private static final String TAG = "FingerprintRemovalClient";
 
+    private final int[] mBiometricIds;
+
     FingerprintRemovalClient(@NonNull Context context,
             @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
-            @Nullable ClientMonitorCallbackConverter listener, int biometricId, int userId,
+            @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        mBiometricIds = biometricIds;
     }
 
     @Override
     protected void startHalOperation() {
         try {
-            final int[] ids = new int[] {mBiometricId};
-            getFreshDaemon().removeEnrollments(mSequentialId, ids);
+            getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
             mCallback.onClientFinished(this, false /* success */);
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 c83c0fb..a98e7db 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
@@ -22,6 +22,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
@@ -439,8 +440,9 @@
         }
     }
 
-    @NonNull ITestSession createTestSession() {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+    @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
+                mProvider, this);
     }
 
     void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 312ee0a..766a882 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -21,13 +21,16 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import java.util.ArrayList;
@@ -47,6 +50,7 @@
 
     @NonNull private final Context mContext;
     private final int mSensorId;
+    @NonNull private final ITestSessionCallback mCallback;
     @NonNull private final Fingerprint21 mFingerprint21;
     @NonNull private final Fingerprint21.HalResultController mHalResultController;
     @NonNull private final Set<Integer> mEnrollmentIds;
@@ -111,10 +115,12 @@
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+            @NonNull ITestSessionCallback callback,
             @NonNull Fingerprint21 fingerprint21,
             @NonNull Fingerprint21.HalResultController halResultController) {
         mContext = context;
         mSensorId = sensorId;
+        mCallback = callback;
         mFingerprint21 = fingerprint21;
         mHalResultController = halResultController;
         mEnrollmentIds = new HashSet<>();
@@ -191,6 +197,25 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFingerprint21.scheduleInternalCleanup(mSensorId, userId);
+        mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                try {
+                    mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                try {
+                    mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 7a74c6a..6e22a79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -30,6 +30,7 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
@@ -158,7 +159,7 @@
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
         public void onUserSwitching(int newUserId) {
-            scheduleInternalCleanup(newUserId);
+            scheduleInternalCleanup(newUserId, null /* callback */);
         }
     };
 
@@ -437,7 +438,7 @@
         Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
         if (halId != 0) {
             scheduleLoadAuthenticatorIds();
-            scheduleInternalCleanup(ActivityManager.getCurrentUser());
+            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
         } else {
             Slog.e(TAG, "Unable to set callback");
             mDaemon = null;
@@ -463,26 +464,33 @@
             for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
-                    scheduleUpdateActiveUserWithoutHandler(targetUserId);
+                    scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */);
                 }
             }
         });
     }
 
+    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+        scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */);
+    }
+
     /**
      * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
      * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
      * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
      * this operation on the same lambda/runnable as those operations so that the ordering is
      * correct.
+     *
+     * @param targetUserId Switch to this user, and update their authenticatorId
+     * @param force Always retrieve the authenticatorId, even if we are already the targetUserId
      */
-    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+    private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
         final boolean hasEnrolled =
                 !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
         final FingerprintUpdateActiveUserClient client =
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
-                        hasEnrolled, mAuthenticatorIds);
+                        hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -563,7 +571,8 @@
                         boolean success) {
                     if (success) {
                         // Update authenticatorIds
-                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                                true /* force */);
                     }
                 }
             });
@@ -636,7 +645,25 @@
         });
     }
 
-    private void scheduleInternalCleanup(int userId) {
+    @Override
+    public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int userId,
+            @NonNull String opPackageName) {
+        mHandler.post(() -> {
+            scheduleUpdateActiveUserWithoutHandler(userId);
+
+            // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
+            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
+                    0 /* fingerprintId */, userId, opPackageName,
+                    FingerprintUtils.getLegacyInstance(mSensorId),
+                    mSensorProperties.sensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
+        });
+    }
+
+    private void scheduleInternalCleanup(int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -646,13 +673,14 @@
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId, enrolledList,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, callback);
         });
     }
 
     @Override
-    public void scheduleInternalCleanup(int sensorId, int userId) {
-        scheduleInternalCleanup(userId);
+    public void scheduleInternalCleanup(int sensorId, int userId,
+            @Nullable BaseClientMonitor.Callback callback) {
+        scheduleInternalCleanup(userId, callback);
     }
 
     @Override
@@ -832,8 +860,9 @@
 
     @NonNull
     @Override
-    public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
-        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this,
+    public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) {
+        return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback, this,
                 mHalResultController);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index f6a22f5..2f360f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -39,13 +39,16 @@
 class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> {
     private static final String TAG = "FingerprintRemovalClient";
 
+    private final int mBiometricId;
+
     FingerprintRemovalClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull Map<Integer, Long> authenticatorIds) {
-        super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId,
+        super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
                 authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        mBiometricId = biometricId;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index db7f4bc..6776810 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -41,6 +41,7 @@
     private static final String FP_DATA_DIR = "fpdata";
 
     private final int mCurrentUserId;
+    private final boolean mForceUpdateAuthenticatorId;
     private final boolean mHasEnrolledBiometrics;
     private final Map<Integer, Long> mAuthenticatorIds;
     private File mDirectory;
@@ -48,11 +49,12 @@
     FingerprintUpdateActiveUserClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics,
-            @NonNull Map<Integer, Long> authenticatorIds) {
+            @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
                 BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
         mCurrentUserId = currentUserId;
+        mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
         mHasEnrolledBiometrics = hasEnrolledBiometrics;
         mAuthenticatorIds = authenticatorIds;
     }
@@ -61,7 +63,7 @@
     public void start(@NonNull Callback callback) {
         super.start(callback);
 
-        if (mCurrentUserId == getTargetUserId()) {
+        if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
             callback.onClientFinished(this, true /* success */);
             return;
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 8e84613..f44e069 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.iris.IIrisService;
 import android.os.IBinder;
@@ -39,7 +40,8 @@
     }
 
     @Override
-    public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException {
+    public ITestSession createTestSession(@NonNull ITestSessionCallback callback,
+            @NonNull String opPackageName) throws RemoteException {
         return null;
     }
 
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/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 8d21f6f..8bf1886 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -83,9 +83,8 @@
     private final INetd mNetd;
     private final Dependencies mDeps;
 
-    // Values are User IDs.
     @GuardedBy("this")
-    private final Set<Integer> mUsers = new HashSet<>();
+    private final Set<UserHandle> mUsers = new HashSet<>();
 
     // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
     @GuardedBy("this")
@@ -173,10 +172,7 @@
             netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
         }
 
-        final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */);
-        for (UserHandle user : users) {
-            mUsers.add(user.getIdentifier());
-        }
+        mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */));
 
         final SparseArray<ArraySet<String>> systemPermission =
                 SystemConfig.getInstance().getSystemPermissions();
@@ -259,16 +255,15 @@
         return array;
     }
 
-    private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
+    private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
         List<Integer> network = new ArrayList<>();
         List<Integer> system = new ArrayList<>();
         for (Entry<Integer, Boolean> app : apps.entrySet()) {
             List<Integer> list = app.getValue() ? system : network;
-            for (int user : users) {
-                final UserHandle handle = UserHandle.of(user);
-                if (handle == null) continue;
+            for (UserHandle user : users) {
+                if (user == null) continue;
 
-                list.add(UserHandle.getUid(handle, app.getKey()));
+                list.add(UserHandle.getUid(user, app.getKey()));
             }
         }
         try {
@@ -291,14 +286,10 @@
      *
      * @hide
      */
-    public synchronized void onUserAdded(int user) {
-        if (user < 0) {
-            loge("Invalid user in onUserAdded: " + user);
-            return;
-        }
+    public synchronized void onUserAdded(@NonNull UserHandle user) {
         mUsers.add(user);
 
-        Set<Integer> users = new HashSet<>();
+        Set<UserHandle> users = new HashSet<>();
         users.add(user);
         update(users, mApps, true);
     }
@@ -310,14 +301,10 @@
      *
      * @hide
      */
-    public synchronized void onUserRemoved(int user) {
-        if (user < 0) {
-            loge("Invalid user in onUserRemoved: " + user);
-            return;
-        }
+    public synchronized void onUserRemoved(@NonNull UserHandle user) {
         mUsers.remove(user);
 
-        Set<Integer> users = new HashSet<>();
+        Set<UserHandle> users = new HashSet<>();
         users.add(user);
         update(users, mApps, false);
     }
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/TEST_MAPPING b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING
new file mode 100644
index 0000000..7fbf426
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "UpdatableSystemFontTest"
+    }
+  ]
+}
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 45f2a38..08ddc6d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -45,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Manages set of updatable font files.
@@ -182,8 +183,12 @@
             List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
             for (int i = 0; i < fontFamilies.size(); i++) {
                 FontConfig.FontFamily fontFamily = fontFamilies.get(i);
-                // Ignore failures as updated fonts may be obsoleted by system OTA update.
-                addFontFamily(fontFamily);
+                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) {
@@ -236,10 +241,7 @@
                                 request.getFd().getFileDescriptor(), request.getSignature());
                         break;
                     case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
-                        // TODO: define error code.
-                        if (!addFontFamily(request.getFontFamily())) {
-                            throw new IllegalArgumentException("Invalid font family");
-                        }
+                        addFontFamily(request.getFontFamily());
                         break;
                 }
             }
@@ -495,18 +497,15 @@
      * Unnamed font families are used as other named font family's fallback fonts to guarantee a
      * complete glyph coverage.
      */
-    private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
-        if (fontFamily.getName() == null) {
-            Slog.e(TAG, "Name is null.");
-            return false;
-        }
+    private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException {
+        Objects.requireNonNull(fontFamily.getName());
         FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
         if (resolvedFontFamily == null) {
-            Slog.e(TAG, "Required fonts are not available");
-            return false;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_NOT_FOUND,
+                    "Required fonts are not available");
         }
         mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
-        return true;
     }
 
     @Nullable
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/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 06bcada..1643ec1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -50,6 +50,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.function.Predicate;
 
@@ -149,6 +150,12 @@
      *         returns {@code null}.
      */
     static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
+        HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(),
+                atomWriter);
+        if (controller != null) {
+            return controller;
+        }
+        HdmiLogger.warning("Unable to use cec@1.1");
         return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
     }
 
@@ -312,7 +319,7 @@
     }
 
     /**
-     * Return CEC version of the device.
+     * Return highest CEC version supported by this device.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
@@ -745,13 +752,202 @@
         boolean nativeIsConnected(int port);
     }
 
-    private static final class NativeWrapperImpl implements NativeWrapper,
+    private static final class NativeWrapperImpl11 implements NativeWrapper,
             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
-        private IHdmiCec mHdmiCec;
+        private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
+        @Nullable private HdmiCecCallback mCallback;
+
         private final Object mLock = new Object();
         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+        @Override
+        public String nativeInit() {
+            return (connectToHal() ? mHdmiCec.toString() : null);
+        }
+
+        boolean connectToHal() {
+            try {
+                mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true);
+                try {
+                    mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
+                } catch (RemoteException e) {
+                    HdmiLogger.error("Couldn't link to death : ", e);
+                }
+            } catch (RemoteException | NoSuchElementException e) {
+                HdmiLogger.error("Couldn't connect to cec@1.1", e);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void onValues(int result, short addr) {
+            if (result == Result.SUCCESS) {
+                synchronized (mLock) {
+                    mPhysicalAddress = new Short(addr).intValue();
+                }
+            }
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
+                HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
+                connectToHal();
+                // Reconnect the callback
+                if (mCallback != null) {
+                    setCallback(mCallback);
+                }
+            }
+        }
+
+        @Override
+        public void setCallback(HdmiCecCallback callback) {
+            mCallback = callback;
+            try {
+                mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
+            } catch (RemoteException e) {
+                HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+            }
+        }
+
+        @Override
+        public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+            android.hardware.tv.cec.V1_1.CecMessage message =
+                    new android.hardware.tv.cec.V1_1.CecMessage();
+            message.initiator = srcAddress;
+            message.destination = dstAddress;
+            message.body = new ArrayList<>(body.length);
+            for (byte b : body) {
+                message.body.add(b);
+            }
+            try {
+                return mHdmiCec.sendMessage_1_1(message);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to send CEC message : ", e);
+                return SendMessageResult.FAIL;
+            }
+        }
+
+        @Override
+        public int nativeAddLogicalAddress(int logicalAddress) {
+            try {
+                return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to add a logical address : ", e);
+                return Result.FAILURE_INVALID_ARGS;
+            }
+        }
+
+        @Override
+        public void nativeClearLogicalAddress() {
+            try {
+                mHdmiCec.clearLogicalAddress();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to clear logical address : ", e);
+            }
+        }
+
+        @Override
+        public int nativeGetPhysicalAddress() {
+            try {
+                mHdmiCec.getPhysicalAddress(this);
+                return mPhysicalAddress;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get physical address : ", e);
+                return INVALID_PHYSICAL_ADDRESS;
+            }
+        }
+
+        @Override
+        public int nativeGetVersion() {
+            try {
+                return mHdmiCec.getCecVersion();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get cec version : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
+        }
+
+        @Override
+        public int nativeGetVendorId() {
+            try {
+                return mHdmiCec.getVendorId();
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get vendor id : ", e);
+                return Result.FAILURE_UNKNOWN;
+            }
+        }
+
+        @Override
+        public HdmiPortInfo[] nativeGetPortInfos() {
+            try {
+                ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
+                        mHdmiCec.getPortInfo();
+                HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
+                int i = 0;
+                for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
+                    hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+                            portInfo.type,
+                            portInfo.physicalAddress,
+                            portInfo.cecSupported,
+                            false,
+                            portInfo.arcSupported);
+                    i++;
+                }
+                return hdmiPortInfo;
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get port information : ", e);
+                return null;
+            }
+        }
+
+        @Override
+        public void nativeSetOption(int flag, boolean enabled) {
+            try {
+                mHdmiCec.setOption(flag, enabled);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set option : ", e);
+            }
+        }
+
+        @Override
+        public void nativeSetLanguage(String language) {
+            try {
+                mHdmiCec.setLanguage(language);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to set language : ", e);
+            }
+        }
+
+        @Override
+        public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+            try {
+                mHdmiCec.enableAudioReturnChannel(port, flag);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to enable/disable ARC : ", e);
+            }
+        }
+
+        @Override
+        public boolean nativeIsConnected(int port) {
+            try {
+                return mHdmiCec.isConnected(port);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Failed to get connection info : ", e);
+                return false;
+            }
+        }
+    }
+
+    private static final class NativeWrapperImpl implements NativeWrapper,
+            IHwBinder.DeathRecipient, getPhysicalAddressCallback {
+        private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
         @Nullable private HdmiCecCallback mCallback;
 
+        private final Object mLock = new Object();
+        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
         @Override
         public String nativeInit() {
             return (connectToHal() ? mHdmiCec.toString() : null);
@@ -765,8 +961,8 @@
                 } catch (RemoteException e) {
                     HdmiLogger.error("Couldn't link to death : ", e);
                 }
-            } catch (RemoteException e) {
-                HdmiLogger.error("Couldn't get tv.cec service : ", e);
+            } catch (RemoteException | NoSuchElementException e) {
+                HdmiLogger.error("Couldn't connect to cec@1.0", e);
                 return false;
             }
             return true;
@@ -776,7 +972,7 @@
         public void setCallback(@NonNull HdmiCecCallback callback) {
             mCallback = callback;
             try {
-                mHdmiCec.setCallback(callback);
+                mHdmiCec.setCallback(new HdmiCecCallback10(callback));
             } catch (RemoteException e) {
                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
             }
@@ -931,20 +1127,69 @@
         }
     }
 
-    final class HdmiCecCallback extends IHdmiCecCallback.Stub {
+    final class HdmiCecCallback {
+        public void onCecMessage(int initiator, int destination, byte[] body) {
+            runOnServiceThread(
+                    () -> handleIncomingCecCommand(initiator, destination, body));
+        }
+
+        public void onHotplugEvent(int portId, boolean connected) {
+            runOnServiceThread(() -> handleHotplug(portId, connected));
+        }
+    }
+
+    private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub {
+        private final HdmiCecCallback mHdmiCecCallback;
+
+        HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
+            mHdmiCecCallback = hdmiCecCallback;
+        }
+
         @Override
         public void onCecMessage(CecMessage message) throws RemoteException {
             byte[] body = new byte[message.body.size()];
             for (int i = 0; i < message.body.size(); i++) {
                 body[i] = message.body.get(i);
             }
-            runOnServiceThread(
-                    () -> handleIncomingCecCommand(message.initiator, message.destination, body));
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
         }
 
         @Override
         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
-            runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
+            mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
+        }
+    }
+
+    private static final class HdmiCecCallback11
+            extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
+        private final HdmiCecCallback mHdmiCecCallback;
+
+        HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
+            mHdmiCecCallback = hdmiCecCallback;
+        }
+
+        @Override
+        public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
+                throws RemoteException {
+            byte[] body = new byte[message.body.size()];
+            for (int i = 0; i < message.body.size(); i++) {
+                body[i] = message.body.get(i);
+            }
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+        }
+
+        @Override
+        public void onCecMessage(CecMessage message) throws RemoteException {
+            byte[] body = new byte[message.body.size()];
+            for (int i = 0; i < message.body.size(); i++) {
+                body[i] = message.body.get(i);
+            }
+            mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+        }
+
+        @Override
+        public void onHotplugEvent(HotplugEvent event) throws RemoteException {
+            mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
         }
     }
 
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/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 04acd51..2ed8481 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -139,8 +139,12 @@
     @ServiceThreadOnly
     void toggleAndFollowTvPower() {
         assertRunOnServiceThread();
-        // Wake up Android framework to take over CEC control from the microprocessor.
-        mService.wakeUp();
+        if (mService.getPowerManager().isInteractive()) {
+            mService.pauseActiveMediaSessions();
+        } else {
+            // Wake up Android framework to take over CEC control from the microprocessor.
+            mService.wakeUp();
+        }
         mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int status) {
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 fa1fb48..d014f14 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -774,8 +774,14 @@
 
     private void initializeCec(int initiatedBy) {
         mAddressAllocated = false;
-        mCecVersion = getHdmiCecConfig().getIntValue(
+        int settingsCecVersion = getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION);
+        int supportedCecVersion = mCecController.getVersion();
+
+        // Limit the used CEC version to the highest supported version by HAL and selected
+        // version in settings (but at least v1.4b).
+        mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
+                Math.min(settingsCecVersion, supportedCecVersion));
 
         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
         mCecController.setLanguage(mMenuLanguage);
@@ -2184,6 +2190,7 @@
 
             pw.println("mProhibitMode: " + mProhibitMode);
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
+            pw.println("mCecVersion: " + mCecVersion);
 
             // System settings
             pw.println("System_settings:");
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
index ee3427f..a1e6136 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
@@ -70,6 +70,10 @@
         pw.println("                --args <vendor specific arguments>");
         pw.println("                [--id <true if vendor command should be sent with vendor id>]");
         pw.println("      Send a Vendor Command to the given target device");
+        pw.println("  cec_setting get <setting name>");
+        pw.println("      Get the current value of a CEC setting");
+        pw.println("  cec_setting set <setting name> <value>");
+        pw.println("      Set the value of a CEC setting");
     }
 
     private int handleShellCommand(String cmd) throws RemoteException {
@@ -81,6 +85,8 @@
                 return oneTouchPlay(pw);
             case "vendorcommand":
                 return vendorCommand(pw);
+            case "cec_setting":
+                return cecSetting(pw);
         }
 
         getErrPrintWriter().println("Unhandled command: " + cmd);
@@ -157,4 +163,39 @@
         mBinderService.sendVendorCommand(deviceType, destination, params, hasVendorId);
         return 0;
     }
+
+    private int cecSetting(PrintWriter pw) throws RemoteException {
+        if (getRemainingArgsCount() < 1) {
+            throw new IllegalArgumentException("Expected at least 1 argument (operation).");
+        }
+        String operation = getNextArgRequired();
+        switch (operation) {
+            case "get": {
+                String setting = getNextArgRequired();
+                try {
+                    String value = mBinderService.getCecSettingStringValue(setting);
+                    pw.println(setting + " = " + value);
+                } catch (IllegalArgumentException e) {
+                    int intValue = mBinderService.getCecSettingIntValue(setting);
+                    pw.println(setting + " = " + intValue);
+                }
+                return 0;
+            }
+            case "set": {
+                String setting = getNextArgRequired();
+                String value = getNextArgRequired();
+                try {
+                    mBinderService.setCecSettingStringValue(setting, value);
+                    pw.println(setting + " = " + value);
+                } catch (IllegalArgumentException e) {
+                    int intValue = Integer.parseInt(value);
+                    mBinderService.setCecSettingIntValue(setting, intValue);
+                    pw.println(setting + " = " + intValue);
+                }
+                return 0;
+            }
+            default:
+                throw new IllegalArgumentException("Unknown operation: " + operation);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 47cb43e..6deb19a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -78,6 +78,7 @@
 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;
@@ -97,6 +98,8 @@
 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;
@@ -108,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;
@@ -120,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;
 
@@ -313,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);
@@ -1368,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;
 
@@ -1391,6 +1411,8 @@
             mAppForegroundHelper = new SystemAppForegroundHelper(context);
             mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
+            mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
+            mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
             mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
             mLocationUsageLogger = new LocationUsageLogger();
         }
@@ -1402,6 +1424,8 @@
             mAppForegroundHelper.onSystemReady();
             mLocationPowerSaveModeHelper.onSystemReady();
             mScreenInteractiveHelper.onSystemReady();
+            mDeviceStationaryHelper.onSystemReady();
+            mDeviceIdleHelper.onSystemReady();
 
             if (mEmergencyCallHelper != null) {
                 mEmergencyCallHelper.onSystemReady();
@@ -1451,6 +1475,16 @@
         }
 
         @Override
+        public DeviceStationaryHelper getDeviceStationaryHelper() {
+            return mDeviceStationaryHelper;
+        }
+
+        @Override
+        public DeviceIdleHelper getDeviceIdleHelper() {
+            return mDeviceIdleHelper;
+        }
+
+        @Override
         public LocationAttributionHelper getLocationAttributionHelper() {
             return mLocationAttributionHelper;
         }
diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
new file mode 100644
index 0000000..85de4bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
@@ -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.server.location.contexthub;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+/**
+ * A class that manages a timer used to keep track of how much time is left before a
+ * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from
+ * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from
+ * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper.
+ *
+ * @hide
+ */
+public class AuthStateDenialTimer {
+    private static final long TIMEOUT_MS = SECONDS.toMillis(60);
+
+    private final ContextHubClientBroker mClient;
+    private final long mNanoAppId;
+    private final Handler mHandler;
+
+    /**
+     * Indicates when the timer should stop in the future.
+     */
+    private long mStopTimeInFuture;
+
+    /**
+     * boolean representing if the timer was cancelled
+     */
+    private boolean mCancelled = false;
+
+    public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) {
+        mClient = client;
+        mNanoAppId = nanoAppId;
+        mHandler = new CountDownHandler(looper);
+    }
+
+    /**
+     * Cancel the countdown.
+     */
+    public synchronized void cancel() {
+        mCancelled = true;
+        mHandler.removeMessages(MSG);
+    }
+
+    /**
+     * Start the countdown.
+     */
+    public synchronized void start() {
+        mCancelled = false;
+        mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS;
+        mHandler.sendMessage(mHandler.obtainMessage(MSG));
+    }
+
+    /**
+     * Called when the timer has expired.
+     */
+    public void onFinish() {
+        mClient.handleAuthStateTimerExpiry(mNanoAppId);
+    }
+
+    // Message type used to trigger the timer.
+    private static final int MSG = 1;
+
+    private class CountDownHandler extends Handler {
+
+        CountDownHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (AuthStateDenialTimer.this) {
+                if (mCancelled) {
+                    return;
+                }
+                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+                if (millisLeft <= 0) {
+                    onFinish();
+                } else {
+                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index cc510fb..6249a06 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -17,31 +17,38 @@
 package com.android.server.location.contexthub;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
 
 import android.Manifest;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.IContexthub;
 import android.hardware.contexthub.V1_0.Result;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.server.location.ClientBrokerProto;
 
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -51,14 +58,53 @@
  * notification callbacks. This class implements the IContextHubClient object, and the implemented
  * APIs must be thread-safe.
  *
+ * Additionally, this class is responsible for enforcing permissions usage and attribution are
+ * handled appropriately for a given client. In general, this works as follows:
+ *
+ * Client sending a message to a nanoapp:
+ * 1) When initially sending a message to nanoapps, clients are by default in a grace period state
+ *    which allows them to always send their first message to nanoapps. This is done to allow
+ *    clients (especially callback clients) to reset their conection to the nanoapp if they are
+ *    killed / restarted (e.g. following a permission revocation).
+ * 2) After the initial message is sent, a check of permissions state is performed. If the
+ *    client doesn't have permissions to communicate, it is placed into the denied grace period
+ *    state and notified so that it can clean up its communication before it is completely denied
+ *    access.
+ * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if
+ *    the client is denied authorization
+ *
+ * Client receiving a message from a nanoapp:
+ * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously.
+ *    If there has been no message between the two before, the auth state is assumed granted.
+ * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes
+ *    all permissions required to consume the message being sent. If both of those checks pass, then
+ *    the message is delivered. Otherwise, it's dropped.
+ *
+ * Client losing or gaining permissions (callback client):
+ * 1) Clients are killed when they lose permissions. This will cause callback clients to completely
+ *    disconnect from the service. When they are restarted, their initial message will still be
+ *    be allowed through and their permissions will be rechecked at that time.
+ * 2) If they gain a permission, the broker will notify them if that permission allows them to
+ *    communicate with a nanoapp again.
+ *
+ * Client losing or gaining permissions (PendingIntent client):
+ * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the
+ *    service when they are killed. In their case, they will receive notifications of the broker
+ *    that they have been denied required permissions or gain required permissions.
+ *
  * TODO: Consider refactoring this class via inheritance
  *
  * @hide
  */
 public class ContextHubClientBroker extends IContextHubClient.Stub
-        implements IBinder.DeathRecipient {
+        implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
     private static final String TAG = "ContextHubClientBroker";
 
+    /**
+     * Message used by noteOp when this client receives a message from a nanoapp.
+     */
+    private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+
     /*
      * The context of the service.
      */
@@ -67,7 +113,7 @@
     /*
      * The proxy to talk to the Context Hub HAL.
      */
-    private final IContexthub mContextHubProxy;
+    private final IContextHubWrapper mContextHubProxy;
 
     /*
      * The manager that registered this client.
@@ -95,6 +141,12 @@
      */
     private boolean mRegistered = true;
 
+    /**
+     * String containing an attribution tag that was denoted in the {@link Context} of the
+     * creator of this broker. This is used when attributing the permissions usage of the broker.
+     */
+    private @Nullable String mAttributionTag;
+
     /*
      * Internal interface used to invoke client callbacks.
      */
@@ -112,6 +164,26 @@
      */
     private final String mPackage;
 
+    /**
+     * The PID associated with this client.
+     */
+    private final int mPid;
+
+    /**
+     * The UID associated with this client.
+     */
+    private final int mUid;
+
+    /**
+     * Manager used for noting permissions usage of this broker.
+     */
+    private final AppOpsManager mAppOpsManager;
+
+    /**
+     * Manager used to queue transactions to the context hub.
+     */
+    private final ContextHubTransactionManager mTransactionManager;
+
     /*
      * True if a PendingIntent has been cancelled.
      */
@@ -123,11 +195,44 @@
     private final boolean mHasAccessContextHubPermission;
 
     /*
-     * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging
-     * channel with, i.e. has sent or received messages from this particular nanoapp.
+     * Map containing all nanoapps this client has a messaging channel with and whether it is
+     * allowed to communicate over that channel. A channel is defined to have been opened if the
+     * client has sent or received messages from the particular nanoapp.
      */
-    private final Set<Long> mMessageChannelNanoappIdSet =
-            Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
+    private final Map<Long, Integer> mMessageChannelNanoappIdMap =
+            new ConcurrentHashMap<Long, Integer>();
+
+    /**
+     * Map containing all nanoapps that have active auth state denial timers.
+     */
+    private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
+            new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+
+    /**
+     * Callback used to obtain the latest set of nanoapp permissions and verify this client has
+     * each nanoapps permissions granted.
+     */
+    private final IContextHubTransactionCallback mQueryPermsCallback =
+            new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
+                    Log.e(TAG, "Permissions query failed, but still received nanoapp state");
+                } else if (nanoAppStateList != null) {
+                    for (NanoAppState state : nanoAppStateList) {
+                        if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
+                            List<String> permissions = state.getNanoAppPermissions();
+                            updateNanoAppAuthState(state.getNanoAppId(),
+                                    hasPermissions(permissions), false /* gracePeriodExpired */);
+                        }
+                    }
+                }
+            }
+        };
 
     /*
      * Helper class to manage registered PendingIntent requests from the client.
@@ -175,37 +280,57 @@
         }
     }
 
-    /* package */ ContextHubClientBroker(
-            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
-            ContextHubInfo contextHubInfo, short hostEndPointId,
-            IContextHubClientCallback callback) {
+    private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
+            long nanoAppId, String packageName) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
         mAttachedContextHubInfo = contextHubInfo;
         mHostEndPointId = hostEndPointId;
         mCallbackInterface = callback;
-        mPendingIntentRequest = new PendingIntentRequest();
-        mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+        if (pendingIntent == null) {
+            mPendingIntentRequest = new PendingIntentRequest();
+        } else {
+            mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
+        }
+        mPackage = packageName;
+        mAttributionTag = attributionTag;
+        mTransactionManager = transactionManager;
 
+        mPid = Binder.getCallingPid();
+        mUid = Binder.getCallingUid();
         mHasAccessContextHubPermission = context.checkCallingPermission(
                 Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+        startMonitoringOpChanges();
     }
 
     /* package */ ContextHubClientBroker(
-            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
-            ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
-            long nanoAppId) {
-        mContext = context;
-        mContextHubProxy = contextHubProxy;
-        mClientManager = clientManager;
-        mAttachedContextHubInfo = contextHubInfo;
-        mHostEndPointId = hostEndPointId;
-        mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
-        mPackage = pendingIntent.getCreatorPackage();
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+            ContextHubTransactionManager transactionManager, String packageName) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback,
+                attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */,
+                packageName);
+    }
 
-        mHasAccessContextHubPermission = context.checkCallingPermission(
-                Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+    /* package */ ContextHubClientBroker(
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
+            String attributionTag, ContextHubTransactionManager transactionManager) {
+        this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId,
+                null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId,
+                pendingIntent.getCreatorPackage());
+    }
+
+    private void startMonitoringOpChanges() {
+        mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this);
     }
 
     /**
@@ -219,9 +344,38 @@
     public int sendMessageToNanoApp(NanoAppMessage message) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
+        int authState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            // Default to the granted auth state. The true auth state will be checked async if it's
+            // not denied.
+            authState = mMessageChannelNanoappIdMap.getOrDefault(
+                    message.getNanoAppId(), AUTHORIZATION_GRANTED);
+            if (authState == AUTHORIZATION_DENIED) {
+                return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+            }
+        }
+
         int result;
         if (isRegistered()) {
-            mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+            // Even though the auth state is currently not denied, query the nanoapp permissions
+            // async and verify that the host app currently holds all the requisite permissions.
+            // This can't be done synchronously due to the async query that needs to be performed to
+            // obtain the nanoapp permissions.
+            boolean initialNanoappMessage = false;
+            synchronized (mMessageChannelNanoappIdMap) {
+                if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
+                    mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
+                    initialNanoappMessage = true;
+                }
+            }
+
+            if (initialNanoappMessage) {
+                // Only check permissions the first time a nanoapp is queried since nanoapp
+                // permissions don't currently change at runtime. If the host permission changes
+                // later, that'll be checked by onOpChanged.
+                checkNanoappPermsAsync();
+            }
+
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
 
@@ -262,6 +416,34 @@
         onClientExit();
     }
 
+    @Override
+    public void onOpChanged(String op, String packageName) {
+        if (packageName.equals(mPackage)) {
+            if (!mMessageChannelNanoappIdMap.isEmpty()) {
+                checkNanoappPermsAsync();
+            }
+        }
+    }
+
+    /* package */ String getPackageName() {
+        return mPackage;
+    }
+
+    /**
+     * Used to override the attribution tag with a newer value if a PendingIntent broker is
+     * retrieved.
+     */
+    /* package */ void setAttributionTag(String attributionTag) {
+        mAttributionTag = attributionTag;
+    }
+
+    /**
+     * @return the attribution tag associated with this broker.
+     */
+    /* package */ String getAttributionTag() {
+        return mAttributionTag;
+    }
+
     /**
      * @return the ID of the context hub this client is attached to
      */
@@ -280,15 +462,39 @@
      * Sends a message to the client associated with this object.
      *
      * @param message the message that came from a nanoapp
+     * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
+     * message
+     * @param messagePermissions permissions required to consume the message being delivered. These
+     * permissions are what will be attributed to the client through noteOp.
      */
-    /* package */ void sendMessageToClient(NanoAppMessage message) {
-        mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+    /* package */ void sendMessageToClient(
+            NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        long nanoAppId = message.getNanoAppId();
+
+        int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+
+        // If in the grace period, the host may not receive any messages containing permissions
+        // covered data.
+        if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " in grace period and napp msg has permissions");
+            return;
+        }
+
+        if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+                || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+            Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+                    + " doesn't have permission");
+            return;
+        }
+
         invokeCallback(callback -> callback.onMessageFromNanoApp(message));
 
         Supplier<Intent> supplier =
-                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
                         .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
-        sendPendingIntent(supplier, message.getNanoAppId());
+        sendPendingIntent(supplier, nanoAppId);
     }
 
     /**
@@ -297,6 +503,10 @@
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
     /* package */ void onNanoAppLoaded(long nanoAppId) {
+        // Check the latest state to see if the loaded nanoapp's permissions changed such that the
+        // host app can communicate with it again.
+        checkNanoappPermsAsync();
+
         invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
         sendPendingIntent(
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId);
@@ -364,6 +574,43 @@
     }
 
     /**
+     * Checks that this client has all of the provided permissions.
+     *
+     * @param permissions list of permissions to check
+     * @return true if the client has all of the permissions granted
+     */
+    /* package */ boolean hasPermissions(List<String> permissions) {
+        for (String permission : permissions) {
+            if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attributes the provided permissions to the package of this client.
+     *
+     * @param permissions list of permissions covering data the client is about to receive
+     * @param noteMessage message that should be noted alongside permissions attribution to
+     * facilitate debugging
+     * @return true if client has ability to use all of the provided permissions
+     */
+    /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+        for (String permission : permissions) {
+            int opCode = mAppOpsManager.permissionToOpCode(permission);
+            if (opCode != AppOpsManager.OP_NONE) {
+                if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
      * @return true if the client is a PendingIntent client that has been cancelled.
      */
     /* package */ boolean isPendingIntentCancelled() {
@@ -371,6 +618,101 @@
     }
 
     /**
+     * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
+     * period.
+     */
+    /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+        AuthStateDenialTimer timer;
+        synchronized (mMessageChannelNanoappIdMap) {
+            timer = mNappToAuthTimerMap.remove(nanoAppId);
+        }
+
+        if (timer != null) {
+            updateNanoAppAuthState(
+                    nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+        }
+    }
+
+    /**
+     * Verifies this client has the permissions to communicate with all of the nanoapps it has
+     * communicated with in the past.
+     */
+    private void checkNanoappPermsAsync() {
+        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+        mTransactionManager.addTransaction(transaction);
+    }
+
+    /**
+     * Updates the latest authentication state for this client to be able to communicate with the
+     * given nanoapp.
+     */
+    private void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
+        updateNanoAppAuthState(
+                nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */);
+    }
+
+    /* package */ void updateNanoAppAuthState(
+            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired,
+            boolean forceDenied) {
+        int curAuthState;
+        int newAuthState;
+        synchronized (mMessageChannelNanoappIdMap) {
+            curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
+                    nanoAppId, AUTHORIZATION_GRANTED);
+            newAuthState = curAuthState;
+            // The below logic ensures that only the following transitions are possible:
+            // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
+            // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires
+            // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again
+            // any state -> DENIED if "forceDenied" is true
+            if (forceDenied) {
+                newAuthState = AUTHORIZATION_DENIED;
+            } else if (gracePeriodExpired) {
+                if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                    newAuthState = AUTHORIZATION_DENIED;
+                }
+            } else {
+                if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) {
+                    newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD;
+                } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) {
+                    newAuthState = AUTHORIZATION_GRANTED;
+                }
+            }
+
+            if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) {
+                AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId);
+                if (timer != null) {
+                    timer.cancel();
+                }
+            } else if (curAuthState == AUTHORIZATION_GRANTED) {
+                AuthStateDenialTimer timer =
+                        new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper());
+                mNappToAuthTimerMap.put(nanoAppId, timer);
+                timer.start();
+            }
+
+            if (curAuthState != newAuthState) {
+                mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState);
+            }
+        }
+        if (curAuthState != newAuthState) {
+            // Don't send the callback in the synchronized block or it could end up in a deadlock.
+            sendAuthStateCallback(nanoAppId, newAuthState);
+        }
+    }
+
+    private void sendAuthStateCallback(long nanoAppId, int authState) {
+        invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState));
+
+        Supplier<Intent> supplier =
+                () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId)
+                        .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState);
+        sendPendingIntent(supplier, nanoAppId);
+    }
+
+    /**
      * Helper function to invoke a specified client callback, if the connection is open.
      *
      * @param consumer the consumer specifying the callback to invoke
@@ -479,6 +821,20 @@
             mClientManager.unregisterClient(mHostEndPointId);
             mRegistered = false;
         }
+        mAppOpsManager.stopWatchingMode(this);
+    }
+
+    private String authStateToString(@ContextHubManager.AuthorizationState int state) {
+        switch (state) {
+            case AUTHORIZATION_DENIED:
+                return "DENIED";
+            case AUTHORIZATION_DENIED_GRACE_PERIOD:
+                return "DENIED_GRACE_PERIOD";
+            case AUTHORIZATION_GRANTED:
+                return "GRANTED";
+            default:
+                return "UNKNOWN";
+        }
     }
 
     /**
@@ -508,17 +864,23 @@
         String out = "[ContextHubClient ";
         out += "endpointID: " + getHostEndPointId() + ", ";
         out += "contextHub: " + getAttachedContextHubId() + ", ";
+        if (mAttributionTag != null) {
+            out += "attributionTag: " + getAttributionTag() + ", ";
+        }
         if (mPendingIntentRequest.isValid()) {
             out += "intentCreatorPackage: " + mPackage + ", ";
             out += "nanoAppId: 0x" + Long.toHexString(mPendingIntentRequest.getNanoAppId());
         } else {
             out += "package: " + mPackage;
         }
-        if (mMessageChannelNanoappIdSet.size() > 0) {
+        if (mMessageChannelNanoappIdMap.size() > 0) {
             out += " messageChannelNanoappSet: (";
-            Iterator<Long> it = mMessageChannelNanoappIdSet.iterator();
+            Iterator<Map.Entry<Long, Integer>> it =
+                    mMessageChannelNanoappIdMap.entrySet().iterator();
             while (it.hasNext()) {
-                out += "0x" + Long.toHexString(it.next());
+                Map.Entry<Long, Integer> entry = it.next();
+                out += "0x" + Long.toHexString(entry.getKey()) + " auth state: "
+                        + authStateToString(entry.getValue());
                 if (it.hasNext()) {
                     out += ",";
                 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index eda89ab..e3522f6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -20,7 +20,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.IContexthub;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -37,6 +36,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -71,7 +71,7 @@
     /*
      * The proxy to talk to the Context Hub.
      */
-    private final IContexthub mContextHubProxy;
+    private final IContextHubWrapper mContextHubProxy;
 
     /*
      * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
@@ -137,8 +137,7 @@
         }
     }
 
-    /* package */ ContextHubClientManager(
-            Context context, IContexthub contextHubProxy) {
+    /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
     }
@@ -148,19 +147,23 @@
      *
      * @param contextHubInfo the object describing the hub this client is attached to
      * @param clientCallback the callback interface of the client to register
+     * @param attributionTag an optional attribution tag within the given package
      *
      * @return the client interface
      *
      * @throws IllegalStateException if max number of clients have already registered
      */
     /* package */ IContextHubClient registerClient(
-            ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback) {
+            ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
+            String attributionTag, ContextHubTransactionManager transactionManager,
+            String packageName) {
         ContextHubClientBroker broker;
         synchronized (this) {
             short hostEndPointId = getHostEndPointId();
             broker = new ContextHubClientBroker(
                     mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                    hostEndPointId, clientCallback);
+                    hostEndPointId, clientCallback, attributionTag, transactionManager,
+                    packageName);
             mHostEndPointIdToClientMap.put(hostEndPointId, broker);
             mRegistrationRecordDeque.add(
                     new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -185,13 +188,15 @@
      * @param pendingIntent  the callback interface of the client to register
      * @param contextHubInfo the object describing the hub this client is attached to
      * @param nanoAppId      the ID of the nanoapp to receive Intent events for
+     * @param attributionTag an optional attribution tag within the given package
      *
      * @return the client interface
      *
      * @throws IllegalStateException    if there were too many registered clients at the service
      */
     /* package */ IContextHubClient registerClient(
-            ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) {
+            ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
+            String attributionTag, ContextHubTransactionManager transactionManager) {
         ContextHubClientBroker broker;
         String registerString = "Regenerated";
         synchronized (this) {
@@ -201,11 +206,16 @@
                 short hostEndPointId = getHostEndPointId();
                 broker = new ContextHubClientBroker(
                         mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                        hostEndPointId, pendingIntent, nanoAppId);
+                        hostEndPointId, pendingIntent, nanoAppId, attributionTag,
+                        transactionManager);
                 mHostEndPointIdToClientMap.put(hostEndPointId, broker);
                 registerString = "Registered";
                 mRegistrationRecordDeque.add(
                         new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
+            } else {
+                // Update the attribution tag to the latest value provided by the client app in
+                // case the app was updated and decided to change its tag.
+                broker.setAttributionTag(attributionTag);
             }
         }
 
@@ -217,9 +227,14 @@
      * Handles a message sent from a nanoapp.
      *
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
-     * @param message      the message send by a nanoapp
+     * @param message the message send by a nanoapp
+     * @param nanoappPermissions the set of permissions the nanoapp holds
+     * @param messagePermissions the set of permissions that should be used for attributing
+     * permissions when this message is consumed by a client
      */
-    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+    /* package */ void onMessageFromNanoApp(
+            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
         NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
 
         if (DEBUG_LOG_ENABLED) {
@@ -227,11 +242,19 @@
         }
 
         if (clientMessage.isBroadcastMessage()) {
-            broadcastMessage(contextHubId, clientMessage);
+            // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
+            // requirements.
+            if (!messagePermissions.isEmpty()) {
+                Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName);
+            }
+
+            broadcastMessage(
+                    contextHubId, clientMessage, nanoappPermissions, messagePermissions);
         } else {
             ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
             if (proxy != null) {
-                proxy.sendMessageToClient(clientMessage);
+                proxy.sendMessageToClient(
+                        clientMessage, nanoappPermissions, messagePermissions);
             } else {
                 Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
                         + message.hostEndPoint + ")");
@@ -296,6 +319,21 @@
     }
 
     /**
+     * Runs a command for each client that is attached to a hub with the given ID.
+     *
+     * @param contextHubId the ID of the hub
+     * @param callback     the command to invoke for the client
+     */
+    /* package */ void forEachClientOfHub(
+            int contextHubId, Consumer<ContextHubClientBroker> callback) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            if (broker.getAttachedContextHubId() == contextHubId) {
+                callback.accept(broker);
+            }
+        }
+    }
+
+    /**
      * Returns an available host endpoint ID.
      *
      * @returns an available host endpoint ID
@@ -326,22 +364,12 @@
      * @param contextHubId the ID of the hub where the nanoapp sent the message from
      * @param message      the message send by a nanoapp
      */
-    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
-        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
-    }
-
-    /**
-     * Runs a command for each client that is attached to a hub with the given ID.
-     *
-     * @param contextHubId the ID of the hub
-     * @param callback     the command to invoke for the client
-     */
-    private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
-        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
-            if (broker.getAttachedContextHubId() == contextHubId) {
-                callback.accept(broker);
-            }
-        }
+    private void broadcastMessage(
+            int contextHubId, NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        forEachClientOfHub(contextHubId,
+                client -> client.sendMessageToClient(
+                        message, nanoappPermissions, messagePermissions));
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 785e674..0737db7 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -27,10 +27,10 @@
 import android.hardware.contexthub.V1_0.AsyncEventType;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.HubAppInfo;
-import android.hardware.contexthub.V1_0.IContexthubCallback;
 import android.hardware.contexthub.V1_0.Result;
 import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.contexthub.V1_2.HubAppInfo;
+import android.hardware.contexthub.V1_2.IContexthubCallback;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubTransaction;
@@ -50,9 +50,12 @@
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.DumpUtils;
@@ -63,6 +66,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -97,6 +101,7 @@
     private final Context mContext;
 
     private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+    private final List<String> mSupportedContextHubPerms;
     private final List<ContextHubInfo> mContextHubInfoList;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
@@ -137,7 +142,9 @@
 
         @Override
         public void handleClientMsg(ContextHubMsg message) {
-            handleClientMessageCallback(mContextHubId, message);
+            handleClientMessageCallback(mContextHubId, message,
+                    Collections.emptyList() /* nanoappPermissions */,
+                    Collections.emptyList() /* messagePermissions */);
         }
 
         @Override
@@ -156,7 +163,21 @@
         }
 
         @Override
-        public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+        public void handleAppsInfo(
+                ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> nanoAppInfoList) {
+            handleQueryAppsCallback(mContextHubId,
+                    ContextHubServiceUtil.toHubAppInfo_1_2(nanoAppInfoList));
+        }
+
+        @Override
+        public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
+                ArrayList<String> messagePermissions) {
+            handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
+                    messagePermissions);
+        }
+
+        @Override
+        public void handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList) {
             handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
         }
     }
@@ -170,34 +191,37 @@
             mClientManager = null;
             mDefaultClientMap = Collections.emptyMap();
             mContextHubIdToInfoMap = Collections.emptyMap();
+            mSupportedContextHubPerms = Collections.emptyList();
             mContextHubInfoList = Collections.emptyList();
             return;
         }
 
-        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper.getHub());
-        mTransactionManager = new ContextHubTransactionManager(
-                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
-
-        List<ContextHub> hubList;
+        Pair<List<ContextHub>, List<String>> hubInfo;
         try {
-            hubList = mContextHubWrapper.getHub().getHubs();
+            hubInfo = mContextHubWrapper.getHubs();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
-            hubList = Collections.emptyList();
+            hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
         }
+
         mContextHubIdToInfoMap = Collections.unmodifiableMap(
-                ContextHubServiceUtil.createContextHubInfoMap(hubList));
+                ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
+        mSupportedContextHubPerms = hubInfo.second;
         mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
+        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
+        mTransactionManager = new ContextHubTransactionManager(
+                mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
         for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
             ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
             IContextHubClient client = mClientManager.registerClient(
-                    contextHubInfo, createDefaultClientCallback(contextHubId));
+                    contextHubInfo, createDefaultClientCallback(contextHubId),
+                    null /* attributionTag */, mTransactionManager, mContext.getPackageName());
             defaultClientMap.put(contextHubId, client);
 
             try {
-                mContextHubWrapper.getHub().registerCallback(
+                mContextHubWrapper.registerCallback(
                         contextHubId, new ContextHubServiceCallback(contextHubId));
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
@@ -344,6 +368,12 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new ContextHubShellCommand(mContext, this).exec(this, in, out, err, args, callback, result);
+    }
+
+    @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
@@ -593,11 +623,15 @@
     /**
      * Handles a unicast or broadcast message from a nanoapp.
      *
-     * @param contextHubId the ID of the hub the message came from
-     * @param message      the message contents
+     * @param contextHubId   the ID of the hub the message came from
+     * @param message        the message contents
+     * @param reqPermissions the permissions required to consume this message
      */
-    private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
-        mClientManager.onMessageFromNanoApp(contextHubId, message);
+    private void handleClientMessageCallback(
+            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
+        mClientManager.onMessageFromNanoApp(
+                contextHubId, message, nanoappPermissions, messagePermissions);
     }
 
     /**
@@ -703,6 +737,7 @@
      * @param contextHubId   the ID of the hub this client is attached to
      * @param clientCallback the client interface to register with the service
      * @param attributionTag an optional attribution tag within the given package
+     * @param packageName    the name of the package creating this client
      * @return the generated client interface, null if registration was unsuccessful
      * @throws IllegalArgumentException if contextHubId is not a valid ID
      * @throws IllegalStateException    if max number of clients have already registered
@@ -711,7 +746,7 @@
     @Override
     public IContextHubClient createClient(
             int contextHubId, IContextHubClientCallback clientCallback,
-            @Nullable String attributionTag) throws RemoteException {
+            @Nullable String attributionTag, String packageName) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -721,7 +756,8 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, clientCallback);
+        return mClientManager.registerClient(
+                contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
     }
 
     /**
@@ -745,7 +781,8 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
+        return mClientManager.registerClient(
+                contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
     }
 
     /**
@@ -886,6 +923,8 @@
         for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
             pw.println(hubInfo);
         }
+        pw.println("Supported permissions: "
+                + Arrays.toString(mSupportedContextHubPerms.toArray()));
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
@@ -902,6 +941,16 @@
         // dump eventLog
     }
 
+    /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) {
+        mClientManager.forEachClientOfHub(contextHubId, client -> {
+            if (client.getPackageName().equals(packageName)) {
+                client.updateNanoAppAuthState(
+                        nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */,
+                        true /* forceDenied */);
+            }
+        });
+    }
+
     private void dump(ProtoOutputStream proto) {
         mContextHubIdToInfoMap.values().forEach(hubInfo -> {
             long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 88ed105..8361253 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -23,8 +23,8 @@
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
 import android.hardware.contexthub.V1_0.HostEndPoint;
-import android.hardware.contexthub.V1_0.HubAppInfo;
 import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_2.HubAppInfo;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppBinary;
@@ -161,7 +161,8 @@
         ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
         for (HubAppInfo appInfo : nanoAppInfoList) {
             nanoAppStateList.add(
-                    new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+                    new NanoAppState(appInfo.info_1_0.appId, appInfo.info_1_0.version,
+                                     appInfo.info_1_0.enabled, appInfo.permissions));
         }
 
         return nanoAppStateList;
@@ -255,4 +256,26 @@
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
         }
     }
+
+    /**
+     * Converts old list of HubAppInfo received from the HAL to V1.2 HubAppInfo objects.
+     *
+     * @param oldInfoList list of V1.0 HubAppInfo objects
+     * @return list of V1.2 HubAppInfo objects
+     */
+    /* package */
+    static ArrayList<HubAppInfo> toHubAppInfo_1_2(
+            ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> oldInfoList) {
+        ArrayList newAppInfo = new ArrayList<HubAppInfo>();
+        for (android.hardware.contexthub.V1_0.HubAppInfo oldInfo : oldInfoList) {
+            HubAppInfo newInfo = new HubAppInfo();
+            newInfo.info_1_0.appId = oldInfo.appId;
+            newInfo.info_1_0.version = oldInfo.version;
+            newInfo.info_1_0.memUsage = oldInfo.memUsage;
+            newInfo.info_1_0.enabled = oldInfo.enabled;
+            newInfo.permissions = new ArrayList<String>();
+            newAppInfo.add(newInfo);
+        }
+        return newAppInfo;
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
new file mode 100644
index 0000000..5ec85e6
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
@@ -0,0 +1,71 @@
+/*
+ * 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.contexthub;
+
+import android.content.Context;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for ContextHubService.
+ *
+ * Use with {@code adb shell cmd contexthub ...}.
+ *
+ * @hide
+ */
+public class ContextHubShellCommand extends ShellCommand {
+
+    // Internal service impl -- must perform security checks before touching.
+    private final ContextHubService mInternal;
+
+    public ContextHubShellCommand(Context context, ContextHubService service) {
+        mInternal = service;
+
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_CONTEXT_HUB, "ContextHubShellCommand");
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if ("deny".equals(cmd)) {
+            return runDisableAuth();
+        }
+
+        return handleDefaultCommands(cmd);
+    }
+
+    private int runDisableAuth() {
+        int contextHubId = Integer.decode(getNextArgRequired());
+        String packageName = getNextArgRequired();
+        long nanoAppId = Long.decode(getNextArgRequired());
+
+        mInternal.denyClientAuthState(contextHubId, packageName, nanoAppId);
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("ContextHub commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  deny [contextHubId] [packageName] [nanoAppId]");
+        pw.println("    Immediately transitions the package's authentication state to denied so");
+        pw.println("    can no longer communciate with the nanoapp.");
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c11e289..3a5c220 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -16,11 +16,17 @@
 package com.android.server.location.contexthub;
 
 import android.annotation.Nullable;
+import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_1.Setting;
 import android.hardware.contexthub.V1_1.SettingValue;
+import android.hardware.contexthub.V1_2.IContexthubCallback;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.NoSuchElementException;
 
 /**
@@ -87,6 +93,23 @@
     }
 
     /**
+     * Calls the appropriate getHubs function depending on the HAL version.
+     */
+    public abstract Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException;
+
+    /**
+     * Calls the appropriate registerCallback function depending on the HAL version.
+     */
+    public abstract void registerCallback(
+            int hubId, IContexthubCallback callback) throws RemoteException;
+
+    /**
+     * Calls the appropriate sendMessageToHub function depending on the HAL version.
+     */
+    public abstract int sendMessageToHub(int hubId,
+            android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
+
+    /**
      * @return A valid instance of Contexthub HAL 1.0.
      */
     public abstract android.hardware.contexthub.V1_0.IContexthub getHub();
@@ -148,6 +171,20 @@
             mHub = hub;
         }
 
+        public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+            return new Pair(mHub.getHubs(), new ArrayList<String>());
+        }
+
+        public void registerCallback(
+                int hubId, IContexthubCallback callback) throws RemoteException {
+            mHub.registerCallback(hubId, callback);
+        }
+
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
+            return mHub.sendMessageToHub(hubId, message);
+        }
+
         public android.hardware.contexthub.V1_0.IContexthub getHub() {
             return mHub;
         }
@@ -188,6 +225,20 @@
             mHub = hub;
         }
 
+        public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+            return new Pair(mHub.getHubs(), new ArrayList<String>());
+        }
+
+        public void registerCallback(
+                int hubId, IContexthubCallback callback) throws RemoteException {
+            mHub.registerCallback(hubId, callback);
+        }
+
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
+            return mHub.sendMessageToHub(hubId, message);
+        }
+
         public android.hardware.contexthub.V1_0.IContexthub getHub() {
             return mHub;
         }
@@ -227,13 +278,40 @@
         }
     }
 
-    private static class ContextHubWrapperV1_2 extends IContextHubWrapper {
-        private android.hardware.contexthub.V1_2.IContexthub mHub;
+    private static class ContextHubWrapperV1_2 extends IContextHubWrapper
+            implements android.hardware.contexthub.V1_2.IContexthub.getHubs_1_2Callback {
+        private final android.hardware.contexthub.V1_2.IContexthub mHub;
+
+        private Pair<List<ContextHub>, List<String>> mHubInfo =
+                new Pair<>(Collections.emptyList(), Collections.emptyList());
 
         ContextHubWrapperV1_2(android.hardware.contexthub.V1_2.IContexthub hub) {
             mHub = hub;
         }
 
+        @Override
+        public void onValues(ArrayList<ContextHub> hubs, ArrayList<String> supportedPermissions) {
+            mHubInfo = new Pair(hubs, supportedPermissions);
+        }
+
+        public Pair<List<ContextHub>, List<String>> getHubs() throws RemoteException {
+            mHub.getHubs_1_2(this);
+            return mHubInfo;
+        }
+
+        public void registerCallback(
+                int hubId, IContexthubCallback callback) throws RemoteException {
+            mHub.registerCallback_1_2(hubId, callback);
+        }
+
+        public int sendMessageToHub(int hubId,
+                android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
+            android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
+                    new android.hardware.contexthub.V1_2.ContextHubMsg();
+            newMessage.msg_1_0 = message;
+            return mHub.sendMessageToHub_1_2(hubId, newMessage);
+        }
+
         public android.hardware.contexthub.V1_0.IContexthub getHub() {
             return mHub;
         }
diff --git a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
index 60109fe..667fb98 100644
--- a/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
+++ b/services/core/java/com/android/server/location/contexthub/NanoAppStateManager.java
@@ -17,7 +17,7 @@
 package com.android.server.location.contexthub;
 
 import android.annotation.Nullable;
-import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_2.HubAppInfo;
 import android.hardware.location.NanoAppInstanceInfo;
 import android.util.Log;
 
@@ -154,8 +154,8 @@
     synchronized void updateCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
         HashSet<Long> nanoAppIdSet = new HashSet<>();
         for (HubAppInfo appInfo : nanoAppInfoList) {
-            handleQueryAppEntry(contextHubId, appInfo.appId, appInfo.version);
-            nanoAppIdSet.add(appInfo.appId);
+            handleQueryAppEntry(contextHubId, appInfo.info_1_0.appId, appInfo.info_1_0.version);
+            nanoAppIdSet.add(appInfo.info_1_0.appId);
         }
 
         Iterator<NanoAppInstanceInfo> iterator = mNanoAppHash.values().iterator();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 865d41f..dbfd0a5 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -60,7 +60,8 @@
     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;
+    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;
@@ -167,6 +168,11 @@
         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) {
@@ -198,6 +204,9 @@
             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:
@@ -332,6 +341,23 @@
         }
     }
 
+    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
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 0e157c2..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();
 
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..736b654
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.Binder;
+import android.os.PowerManager;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.util.Objects;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceIdleHelper extends DeviceIdleHelper {
+
+    private final Context mContext;
+
+    private PowerManager mPowerManager;
+
+    private boolean mSystemReady;
+    private boolean mRegistrationRequired;
+    private @Nullable BroadcastReceiver mReceiver;
+
+    public SystemDeviceIdleHelper(Context context) {
+        mContext = context;
+    }
+
+    public synchronized void onSystemReady() {
+        mSystemReady = true;
+        mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+        onRegistrationStateChanged();
+    }
+
+    @Override
+    protected synchronized void registerInternal() {
+        mRegistrationRequired = true;
+        onRegistrationStateChanged();
+    }
+
+    @Override
+    protected synchronized void unregisterInternal() {
+        mRegistrationRequired = false;
+        onRegistrationStateChanged();
+    }
+
+    private void onRegistrationStateChanged() {
+        if (!mSystemReady) {
+            return;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mRegistrationRequired && mReceiver == null) {
+                BroadcastReceiver receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        notifyDeviceIdleChanged();
+                    }
+                };
+                mContext.registerReceiver(receiver,
+                        new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                        FgThread.getHandler());
+                mReceiver = receiver;
+            } else if (!mRegistrationRequired && mReceiver != null) {
+                BroadcastReceiver receiver = mReceiver;
+                mReceiver = null;
+                mContext.unregisterReceiver(receiver);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public boolean isDeviceIdle() {
+        Preconditions.checkState(mPowerManager != null);
+        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..9874ecf
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.os.Binder;
+
+import com.android.internal.util.Preconditions;
+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 DeviceIdleInternal mDeviceIdle;
+
+    public SystemDeviceStationaryHelper() {}
+
+    public void onSystemReady() {
+        mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
+    }
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.registerStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        Preconditions.checkState(mDeviceIdle != null);
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mDeviceIdle.unregisterStationaryListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
index 0d7bcb0..03ade5f 100644
--- a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
@@ -68,7 +68,7 @@
         mReady = true;
     }
 
-    private void onScreenInteractiveChanged(boolean interactive) {
+    void onScreenInteractiveChanged(boolean interactive) {
         if (interactive == mIsInteractive) {
             return;
         }
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/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 685e9e6..8da2d67 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2902,11 +2902,8 @@
         FingerprintManager mFingerprintManager = mInjector.getFingerprintManager();
         if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
             if (mFingerprintManager.hasEnrolledFingerprints(userId)) {
-                CountDownLatch latch = new CountDownLatch(1);
-                // For the purposes of M and N, groupId is the same as userId.
-                Fingerprint finger = new Fingerprint(null, userId, 0, 0);
-                mFingerprintManager.remove(finger, userId,
-                        fingerprintManagerRemovalCallback(latch));
+                final CountDownLatch latch = new CountDownLatch(1);
+                mFingerprintManager.removeAll(userId, fingerprintManagerRemovalCallback(latch));
                 try {
                     latch.await(10000, TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
@@ -2920,9 +2917,8 @@
         FaceManager mFaceManager = mInjector.getFaceManager();
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             if (mFaceManager.hasEnrolledTemplates(userId)) {
-                CountDownLatch latch = new CountDownLatch(1);
-                Face face = new Face(null, 0, 0);
-                mFaceManager.remove(face, userId, faceManagerRemovalCallback(latch));
+                final CountDownLatch latch = new CountDownLatch(1);
+                mFaceManager.removeAll(userId, faceManagerRemovalCallback(latch));
                 try {
                     latch.await(10000, TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
@@ -2936,10 +2932,8 @@
             CountDownLatch latch) {
         return new FingerprintManager.RemovalCallback() {
             @Override
-            public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence err) {
-                Slog.e(TAG, String.format(
-                        "Can't remove fingerprint %d in group %d. Reason: %s",
-                        fp.getBiometricId(), fp.getGroupId(), err));
+            public void onRemovalError(@Nullable Fingerprint fp, int errMsgId, CharSequence err) {
+                Slog.e(TAG, "Unable to remove fingerprint, error: " + err);
                 latch.countDown();
             }
 
@@ -2955,9 +2949,8 @@
     private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) {
         return new FaceManager.RemovalCallback() {
             @Override
-            public void onRemovalError(Face face, int errMsgId, CharSequence err) {
-                Slog.e(TAG, String.format("Can't remove face %d. Reason: %s",
-                        face.getBiometricId(), err));
+            public void onRemovalError(@Nullable Face face, int errMsgId, CharSequence err) {
+                Slog.e(TAG, "Unable to remove face, error: " + err);
                 latch.countDown();
             }
 
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 53b62ca..30ea555 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -146,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();
@@ -272,6 +273,10 @@
         // generated before reboot. Note that we will clear the escrow key even if the keystore key
         // is null.
         SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
+        if (kk == null) {
+            Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
+        }
+
         RebootEscrowKey escrowKey;
         try {
             escrowKey = getAndClearRebootEscrowKey(kk);
@@ -281,7 +286,7 @@
             return;
         }
 
-        if (kk == null || escrowKey == null) {
+        if (escrowKey == null) {
             onGetRebootEscrowKeyFailed(users);
             return;
         }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index 9d09637..b3b4546 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -136,6 +136,11 @@
             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 {
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index d48b9a4..639dda6 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -83,7 +83,14 @@
         @Override
         public void reportPlaybackStateEvent(
                 String sessionId, PlaybackStateEvent event, int userId) {
-            // TODO: log it to statsd
+            StatsEvent statsEvent = StatsEvent.newBuilder()
+                    .setAtomId(322)
+                    .writeString(sessionId)
+                    .writeInt(event.getState())
+                    .writeLong(event.getTimeSinceCreatedMillis())
+                    .usePooledBuffer()
+                    .build();
+            StatsLog.write(statsEvent);
         }
 
         @Override
@@ -103,7 +110,7 @@
                     .writeString(event.getExceptionStack())
                     .writeInt(event.getErrorCode())
                     .writeInt(event.getSubErrorCode())
-                    .writeLong(event.getTimeSincePlaybackCreatedMillis())
+                    .writeLong(event.getTimeSinceCreatedMillis())
                     .usePooledBuffer()
                     .build();
             StatsLog.write(statsEvent);
@@ -114,8 +121,8 @@
             StatsEvent statsEvent = StatsEvent.newBuilder()
                     .setAtomId(321)
                     .writeString(sessionId)
-                    .writeInt(event.getType())
-                    .writeLong(event.getTimeSincePlaybackCreatedMillis())
+                    .writeInt(event.getNetworkType())
+                    .writeLong(event.getTimeSinceCreatedMillis())
                     .usePooledBuffer()
                     .build();
             StatsLog.write(statsEvent);
@@ -125,7 +132,7 @@
         public void reportTrackChangeEvent(
                 String sessionId, TrackChangeEvent event, int userId) {
             StatsEvent statsEvent = StatsEvent.newBuilder()
-                    .setAtomId(321)
+                    .setAtomId(324)
                     .writeString(sessionId)
                     .writeInt(event.getTrackState())
                     .writeInt(event.getTrackChangeReason())
@@ -133,7 +140,7 @@
                     .writeString(event.getSampleMimeType())
                     .writeString(event.getCodecName())
                     .writeInt(event.getBitrate())
-                    .writeLong(event.getTimeSincePlaybackCreatedMillis())
+                    .writeLong(event.getTimeSinceCreatedMillis())
                     .writeInt(event.getTrackType())
                     .writeString(event.getLanguage())
                     .writeString(event.getLanguageRegion())
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 905733c..dbd1211 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1341,7 +1341,9 @@
         }
 
         public void forgetAllPackageInfos(final int userId) {
-            for (int i = 0, n = mCache.size(); i < n; i++) {
+            // Iterate in reverse order since removing the package in all users will remove the
+            // package from the cache.
+            for (int i = mCache.size() - 1; i >= 0; i--) {
                 removePackageUser(mCache.valueAt(i), userId);
             }
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index fb183f5..799ab46 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -244,7 +244,11 @@
     @NonNull
     Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        // Always update the overlays of newly added packages.
+        updatedTargets.add(new PackageAndUser(pkgName, userId));
+        updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
+        return updatedTargets;
     }
 
     @NonNull
@@ -282,7 +286,7 @@
     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) );
+                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);
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index a83edb7..9984bfa 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -16,17 +16,29 @@
 
 package com.android.server.os;
 
+import static android.app.ApplicationExitInfo.REASON_CRASH_NATIVE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
 import android.annotation.AppIdInt;
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ApplicationExitInfo;
+import android.app.IParcelFileDescriptorRetriever;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.FileObserver;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoInputStream;
@@ -34,6 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
+import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
 
 import libcore.io.IoUtils;
@@ -42,7 +55,11 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
 /**
  * A class to manage native tombstones.
@@ -75,6 +92,9 @@
     }
 
     void onSystemReady() {
+        registerForUserRemoval();
+        registerForPackageRemoval();
+
         // Scan existing tombstones.
         mHandler.post(() -> {
             final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -94,8 +114,9 @@
 
         if (filename.endsWith(".pb")) {
             handleProtoTombstone(path);
+            BootReceiver.addTombstoneToDropBox(mContext, path, true);
         } else {
-            BootReceiver.addTombstoneToDropBox(mContext, path);
+            BootReceiver.addTombstoneToDropBox(mContext, path, false);
         }
     }
 
@@ -145,18 +166,164 @@
         }
     }
 
+    /**
+     * Remove native tombstones matching a user and/or app.
+     *
+     * @param userId user id to filter by, selects all users if empty
+     * @param appId app id to filter by, selects all users if empty
+     */
+    public void purge(Optional<Integer> userId, Optional<Integer> appId) {
+        mHandler.post(() -> {
+            synchronized (mLock) {
+                for (int i = mTombstones.size() - 1; i >= 0; --i) {
+                    TombstoneFile tombstone = mTombstones.valueAt(i);
+                    if (tombstone.matches(userId, appId)) {
+                        tombstone.purge();
+                        mTombstones.removeAt(i);
+                    }
+                }
+            }
+        });
+    }
+
+    private void purgePackage(int uid, boolean allUsers) {
+        final int appId = UserHandle.getAppId(uid);
+        Optional<Integer> userId;
+        if (allUsers) {
+            userId = Optional.empty();
+        } else {
+            userId = Optional.of(UserHandle.getUserId(uid));
+        }
+        purge(userId, Optional.of(appId));
+    }
+
+    private void purgeUser(int uid) {
+        purge(Optional.of(uid), Optional.empty());
+    }
+
+    private void registerForPackageRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                if (uid == UserHandle.USER_NULL) return;
+
+                final boolean allUsers = intent.getBooleanExtra(
+                        Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+
+                purgePackage(uid, allUsers);
+            }
+        }, filter, null, mHandler);
+    }
+
+    private void registerForUserRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId < 1) return;
+
+                purgeUser(userId);
+            }
+        }, filter, null, mHandler);
+    }
+
+    /**
+     * Collect native tombstones.
+     *
+     * @param output list to append to
+     * @param callingUid POSIX uid to filter by
+     * @param pid pid to filter by, ignored if zero
+     * @param maxNum maximum number of elements in output
+     */
+    public void collectTombstones(ArrayList<ApplicationExitInfo> output, int callingUid, int pid,
+            int maxNum) {
+        CompletableFuture<Object> future = new CompletableFuture<>();
+
+        if (!UserHandle.isApp(callingUid)) {
+            return;
+        }
+
+        final int userId = UserHandle.getUserId(callingUid);
+        final int appId = UserHandle.getAppId(callingUid);
+
+        mHandler.post(() -> {
+            boolean appendedTombstones = false;
+
+            synchronized (mLock) {
+                final int tombstonesSize = mTombstones.size();
+
+            tombstoneIter:
+                for (int i = 0; i < tombstonesSize; ++i) {
+                    TombstoneFile tombstone = mTombstones.valueAt(i);
+                    if (tombstone.matches(Optional.of(userId), Optional.of(appId))) {
+                        if (pid != 0 && tombstone.mPid != pid) {
+                            continue;
+                        }
+
+                        // Try to attach to an existing REASON_CRASH_NATIVE.
+                        final int outputSize = output.size();
+                        for (int j = 0; j < outputSize; ++j) {
+                            ApplicationExitInfo exitInfo = output.get(j);
+                            if (tombstone.matches(exitInfo)) {
+                                exitInfo.setNativeTombstoneRetriever(tombstone.getPfdRetriever());
+                                continue tombstoneIter;
+                            }
+                        }
+
+                        if (output.size() < maxNum) {
+                            appendedTombstones = true;
+                            output.add(tombstone.toAppExitInfo());
+                        }
+                    }
+                }
+            }
+
+            if (appendedTombstones) {
+                Collections.sort(output, (lhs, rhs) -> {
+                    // Reports should be ordered with newest reports first.
+                    long diff = rhs.getTimestamp() - lhs.getTimestamp();
+                    if (diff < 0) {
+                        return -1;
+                    } else if (diff == 0) {
+                        return 0;
+                    } else {
+                        return 1;
+                    }
+                });
+            }
+            future.complete(null);
+        });
+
+        try {
+            future.get();
+        } catch (ExecutionException | InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
-        final @UserIdInt int mUserId;
-        final @AppIdInt int mAppId;
+        @UserIdInt int mUserId;
+        @AppIdInt int mAppId;
+
+        int mPid;
+        int mUid;
+        String mProcessName;
+        @CurrentTimeMillisLong long mTimestampMs;
+        String mCrashReason;
 
         boolean mPurged = false;
+        final IParcelFileDescriptorRetriever mRetriever = new ParcelFileDescriptorRetriever();
 
-        TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) {
+        TombstoneFile(ParcelFileDescriptor pfd) {
             mPfd = pfd;
-            mUserId = userId;
-            mAppId = appId;
         }
 
         public boolean matches(Optional<Integer> userId, Optional<Integer> appId) {
@@ -175,24 +342,90 @@
             return true;
         }
 
+        public boolean matches(ApplicationExitInfo exitInfo) {
+            if (exitInfo.getReason() != REASON_CRASH_NATIVE) {
+                return false;
+            }
+
+            if (exitInfo.getPid() != mPid) {
+                return false;
+            }
+
+            if (exitInfo.getRealUid() != mUid) {
+                return false;
+            }
+
+            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
+                return false;
+            }
+
+            return true;
+        }
+
         public void dispose() {
             IoUtils.closeQuietly(mPfd);
         }
 
+        public void purge() {
+            if (!mPurged) {
+                // There's no way to atomically unlink a specific file for which we have an fd from
+                // a path, which means that we can't safely delete a tombstone without coordination
+                // with tombstoned (which has a risk of deadlock if for example, system_server hangs
+                // with a flock). Do the next best thing, and just truncate the file.
+                //
+                // We don't have to worry about inflicting a SIGBUS on a process that has the
+                // tombstone mmaped, because we only clear if the package has been removed, which
+                // means no one with access to the tombstone should be left.
+                try {
+                    Os.ftruncate(mPfd.getFileDescriptor(), 0);
+                } catch (ErrnoException ex) {
+                    Slog.e(TAG, "Failed to truncate tombstone", ex);
+                }
+                mPurged = true;
+            }
+        }
+
         static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
             final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
             final ProtoInputStream stream = new ProtoInputStream(is);
 
+            int pid = 0;
             int uid = 0;
+            String processName = "";
+            String crashReason = "";
             String selinuxLabel = "";
 
             try {
                 while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.PID:
+                            pid = stream.readInt(Tombstone.PID);
+                            break;
+
                         case (int) Tombstone.UID:
                             uid = stream.readInt(Tombstone.UID);
                             break;
 
+                        case (int) Tombstone.PROCESS_NAME:
+                            processName = stream.readString(Tombstone.PROCESS_NAME);
+                            break;
+
+                        case (int) Tombstone.CAUSE:
+                            long token = stream.start(Tombstone.CAUSE);
+                        cause:
+                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                switch (stream.getFieldNumber()) {
+                                    case (int) Cause.HUMAN_READABLE:
+                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
+                                        break cause;
+
+                                    default:
+                                        break;
+                                }
+                            }
+                            stream.end(token);
+
+
                         case (int) Tombstone.SELINUX_LABEL:
                             selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
                             break;
@@ -211,6 +444,14 @@
                 return Optional.empty();
             }
 
+            long timestampMs = 0;
+            try {
+                StructStat stat = Os.fstat(pfd.getFileDescriptor());
+                timestampMs = stat.st_atim.tv_sec * 1000 + stat.st_atim.tv_nsec / 1000000;
+            } catch (ErrnoException ex) {
+                Slog.e(TAG, "Failed to get timestamp of tombstone", ex);
+            }
+
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
@@ -219,7 +460,74 @@
                 return Optional.empty();
             }
 
-            return Optional.of(new TombstoneFile(pfd, userId, appId));
+            TombstoneFile result = new TombstoneFile(pfd);
+
+            result.mUserId = userId;
+            result.mAppId = appId;
+            result.mPid = pid;
+            result.mUid = uid;
+            result.mProcessName = processName;
+            result.mTimestampMs = timestampMs;
+            result.mCrashReason = crashReason;
+
+            return Optional.of(result);
+        }
+
+        public IParcelFileDescriptorRetriever getPfdRetriever() {
+            return mRetriever;
+        }
+
+        public ApplicationExitInfo toAppExitInfo() {
+            ApplicationExitInfo info = new ApplicationExitInfo();
+            info.setPid(mPid);
+            info.setRealUid(mUid);
+            info.setPackageUid(mUid);
+            info.setDefiningUid(mUid);
+            info.setProcessName(mProcessName);
+            info.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+
+            // Signal numbers are architecture-specific!
+            // We choose to provide nothing here, to avoid leading users astray.
+            info.setStatus(0);
+
+            // No way for us to find out.
+            info.setImportance(RunningAppProcessInfo.IMPORTANCE_GONE);
+            info.setPackageName("");
+            info.setProcessStateSummary(null);
+
+            // We could find out, but they didn't get OOM-killed...
+            info.setPss(0);
+            info.setRss(0);
+
+            info.setTimestamp(mTimestampMs);
+            info.setDescription(mCrashReason);
+
+            info.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+            info.setNativeTombstoneRetriever(mRetriever);
+
+            return info;
+        }
+
+
+        class ParcelFileDescriptorRetriever extends IParcelFileDescriptorRetriever.Stub {
+            ParcelFileDescriptorRetriever() {}
+
+            public @Nullable ParcelFileDescriptor getPfd() {
+                if (mPurged) {
+                    return null;
+                }
+
+                // Reopen the file descriptor as read-only.
+                try {
+                    final String path = "/proc/self/fd/" + mPfd.getFd();
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(path),
+                            MODE_READ_ONLY);
+                    return pfd;
+                } catch (FileNotFoundException ex) {
+                    Slog.e(TAG, "failed to reopen file descriptor as read-only", ex);
+                    return null;
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e91bb46..d3a56c6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,11 +16,13 @@
 
 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;
 import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
 import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -935,6 +937,17 @@
             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);
         }
@@ -1158,6 +1171,8 @@
                 ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
             } else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
                 ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
+            } else if (cacheFlags == FLAG_CACHE_PEOPLE_TILE_SHORTCUTS) {
+                ret = ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
             }
             Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7b9cf73..e6789d4 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;
 
 /**
@@ -2586,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) {
@@ -2676,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);
                 }
@@ -2780,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;
@@ -3026,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;
@@ -3953,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");
@@ -9369,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;
                         }
                     }
@@ -9420,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.
      */
@@ -9862,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,
@@ -25917,6 +25925,17 @@
         }
 
         @Override
+        public boolean isPackageDebuggable(String packageName) throws RemoteException {
+            int callingUser = UserHandle.getCallingUserId();
+            ApplicationInfo appInfo = getApplicationInfo(packageName, 0, callingUser);
+            if (appInfo != null) {
+                return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+            }
+
+            throw new RemoteException("Couldn't get debug flag for package " + packageName);
+        }
+
+        @Override
         public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames)
                 throws RemoteException {
             int callingUser = UserHandle.getUserId(Binder.getCallingUid());
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 a8a6bce..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);
 
@@ -3389,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;
@@ -4582,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/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
index 9588a27..ec643f5 100644
--- a/services/core/java/com/android/server/pm/SettingsXml.java
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -181,7 +181,10 @@
         }
 
         private void moveToFirstTag() throws IOException, XmlPullParserException {
-            // Move to first tag
+            if (mParser.getEventType() == XmlPullParser.START_TAG) {
+                return;
+            }
+
             int type;
             //noinspection StatementWithEmptyBody
             while ((type = mParser.next()) != XmlPullParser.START_TAG
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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 753f22d..24c27be 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1522,6 +1522,14 @@
     }
 
     @Override
+    public boolean isUserForeground() {
+        int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+        int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
+        // TODO(b/179163496): should return true for profile users of the current user as well
+        return currentUser == callingUserId;
+    }
+
+    @Override
     public String getUserName() {
         if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
             throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
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/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/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 50fd6e3..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
@@ -211,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);
 
     /**
@@ -231,8 +325,7 @@
             throws IllegalArgumentException, NameNotFoundException;
 
 
-    interface Connection extends DomainVerificationEnforcer.Callback,
-            Function<String, PackageSetting> {
+    interface Connection extends DomainVerificationEnforcer.Callback {
 
         /**
          * Notify that a settings change has been made and that eventually
@@ -265,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 fa03274..e5ed774 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;
@@ -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;
@@ -401,7 +407,7 @@
             }
 
             pkgState.getOrCreateUserSelectionState(userId)
-                    .setDisallowLinkHandling(!allowed);
+                    .setLinkHandlingAllowed(allowed);
         }
 
         mConnection.scheduleWriteSettings();
@@ -423,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);
                     }
                 }
 
@@ -440,7 +446,7 @@
                 }
 
                 pkgState.getOrCreateUserSelectionState(userId)
-                        .setDisallowLinkHandling(!allowed);
+                        .setLinkHandlingAllowed(allowed);
             }
         }
 
@@ -468,6 +474,17 @@
                 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 */, callingUid, userId);
             DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
@@ -589,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++) {
@@ -601,7 +618,7 @@
             }
 
             return new DomainVerificationUserSelection(pkgState.getId(), packageName,
-                    UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+                    UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
         }
     }
 
@@ -683,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);
                 }
             }
@@ -910,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
@@ -919,7 +936,7 @@
             @Nullable Function<String, PackageSetting> pkgSettingFunction)
             throws NameNotFoundException {
         if (pkgSettingFunction == null) {
-            pkgSettingFunction = mConnection;
+            pkgSettingFunction = mConnection::getPackageSettingLocked;
         }
 
         synchronized (mLock) {
@@ -1156,46 +1173,212 @@
         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<>();
+        int approvalsSize = packageApprovals.size();
+        for (int index = 0; index < approvalsSize; 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;
             }
         }
 
@@ -1203,59 +1386,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
@@ -1264,24 +1464,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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6a441f1..4ccd57d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -56,13 +56,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -2292,25 +2290,6 @@
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
-    @Override
-    public boolean canBeHiddenByKeyguardLw(WindowState win) {
-
-        // Keyguard visibility of window from activities are determined over activity visibility.
-        if (win.getAppToken() != null) {
-            return false;
-        }
-        switch (win.getAttrs().type) {
-            case TYPE_NOTIFICATION_SHADE:
-            case TYPE_STATUS_BAR:
-            case TYPE_NAVIGATION_BAR:
-            case TYPE_WALLPAPER:
-                return false;
-            default:
-                // Hide only windows below the keyguard host window.
-                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
-        }
-    }
-
     /** {@inheritDoc} */
     @Override
     public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index db33e75..b5a9aca 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -672,7 +672,22 @@
     /**
      * @return whether {@param win} can be hidden by Keyguard
      */
-    public boolean canBeHiddenByKeyguardLw(WindowState win);
+    default boolean canBeHiddenByKeyguardLw(WindowState win) {
+        // Keyguard visibility of window from activities are determined over activity visibility.
+        if (win.getAppToken() != null) {
+            return false;
+        }
+        switch (win.getAttrs().type) {
+            case TYPE_NOTIFICATION_SHADE:
+            case TYPE_STATUS_BAR:
+            case TYPE_NAVIGATION_BAR:
+            case TYPE_WALLPAPER:
+                return false;
+            default:
+                // Hide only windows below the keyguard host window.
+                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
+        }
+    }
 
     /**
      * Called when the system would like to show a UI to indicate that an
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 88fdc4a..c0b8202 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -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/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 6d9cb75..2a95416 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -47,7 +47,8 @@
     private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR;
 
     private final ReentrantLock mLock = new ReentrantLock();
-    private File mDataStorageDir;
+    private final File mDataStorageDir;
+    private final String mDataStorageFilename;
     private final FileRotator mFileRotator;
 
     private static class DataElement {
@@ -168,6 +169,7 @@
     public PowerStatsDataStorage(Context context, File dataStoragePath,
             String dataStorageFilename) {
         mDataStorageDir = dataStoragePath;
+        mDataStorageFilename = dataStorageFilename;
 
         if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) {
             Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath());
@@ -177,33 +179,35 @@
             // filename, so any files that don't match the current version number can be deleted.
             File[] files = mDataStorageDir.listFiles();
             for (int i = 0; i < files.length; i++) {
-                // Meter and model files are stored in the same directory.
+                // Meter, model, and residency files are stored in the same directory.
                 //
                 // The format of filenames on disk is:
                 //    log.powerstats.meter.version.timestamp
                 //    log.powerstats.model.version.timestamp
+                //    log.powerstats.residency.version.timestamp
                 //
                 // The format of dataStorageFilenames is:
                 //    log.powerstats.meter.version
                 //    log.powerstats.model.version
+                //    log.powerstats.residency.version
                 //
-                // A PowerStatsDataStorage object is created for meter and model data.  Strip off
-                // the version and check that the current file we're checking starts with the stem
-                // (log.powerstats.meter or log.powerstats.model). If the stem matches and the
-                // version number is different, delete the old file.
-                int versionDot = dataStorageFilename.lastIndexOf('.');
-                String beforeVersionDot = dataStorageFilename.substring(0, versionDot);
+                // A PowerStatsDataStorage object is created for meter, model, and residency data.
+                // Strip off the version and check that the current file we're checking starts with
+                // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency).
+                // If the stem matches and the version number is different, delete the old file.
+                int versionDot = mDataStorageFilename.lastIndexOf('.');
+                String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
                 // Check that the stems match.
                 if (files[i].getName().startsWith(beforeVersionDot)) {
                     // Check that the version number matches.  If not, delete the old file.
-                    if (!files[i].getName().startsWith(dataStorageFilename)) {
+                    if (!files[i].getName().startsWith(mDataStorageFilename)) {
                         files[i].delete();
                     }
                 }
             }
 
             mFileRotator = new FileRotator(mDataStorageDir,
-                                           dataStorageFilename,
+                                           mDataStorageFilename,
                                            ROTATE_AGE_MILLIS,
                                            DELETE_AGE_MILLIS);
         }
@@ -242,4 +246,19 @@
     public void read(DataElementReadCallback callback) throws IOException {
         mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE);
     }
+
+    /**
+     * Deletes all stored log data.
+     */
+    public void deleteLogs() {
+        File[] files = mDataStorageDir.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            int versionDot = mDataStorageFilename.lastIndexOf('.');
+            String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+            // Check that the stems before the version match.
+            if (files[i].getName().startsWith(beforeVersionDot)) {
+                files[i].delete();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 37fc5a0..c4f29ea 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -41,14 +42,17 @@
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 /**
- * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
- * Messages are sent to its message handler to request that energy data be logged, at which time it
- * queries the PowerStats HAL and logs the data to on-device storage.  The on-device storage is
- * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile
- * with a file descriptor that points to the output file.
+ * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device
+ * storage.  Messages are sent to its message handler to request that energy data be logged, at
+ * which time it queries the PowerStats HAL and logs the data to on-device storage.  The on-device
+ * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or
+ * writeResidencyDataToFile with a file descriptor that points to the output file.
  */
 public final class PowerStatsLogger extends Handler {
     private static final String TAG = PowerStatsLogger.class.getSimpleName();
@@ -61,6 +65,10 @@
     private final PowerStatsDataStorage mPowerStatsModelStorage;
     private final PowerStatsDataStorage mPowerStatsResidencyStorage;
     private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private File mDataStoragePath;
+    private boolean mDeleteMeterDataOnBoot;
+    private boolean mDeleteModelDataOnBoot;
+    private boolean mDeleteResidencyDataOnBoot;
 
     @Override
     public void handleMessage(Message msg) {
@@ -230,16 +238,99 @@
         pos.flush();
     }
 
-    public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
-            String modelFilename, String residencyFilename,
+    private boolean dataChanged(String cachedFilename, byte[] dataCurrent) {
+        boolean dataChanged = false;
+
+        if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) {
+            final File cachedFile = new File(mDataStoragePath, cachedFilename);
+
+            if (cachedFile.exists()) {
+                // Get the byte array for the cached data.
+                final byte[] dataCached = new byte[(int) cachedFile.length()];
+
+                // Get the cached data from file.
+                try {
+                    final FileInputStream fis = new FileInputStream(cachedFile.getPath());
+                    fis.read(dataCached);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to read cached data from file");
+                }
+
+                // If the cached and current data are different, delete the data store.
+                dataChanged = !Arrays.equals(dataCached, dataCurrent);
+            } else {
+                // Either the cached file was somehow deleted, or this is the first
+                // boot of the device and we're creating the file for the first time.
+                // In either case, delete the log files.
+                dataChanged = true;
+            }
+        }
+
+        return dataChanged;
+    }
+
+    private void updateCacheFile(String cacheFilename, byte[] data) {
+        try {
+            final AtomicFile atomicCachedFile =
+                    new AtomicFile(new File(mDataStoragePath, cacheFilename));
+            final FileOutputStream fos = atomicCachedFile.startWrite();
+            fos.write(data);
+            atomicCachedFile.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write current data to cached file");
+        }
+    }
+
+    public boolean getDeleteMeterDataOnBoot() {
+        return mDeleteMeterDataOnBoot;
+    }
+
+    public boolean getDeleteModelDataOnBoot() {
+        return mDeleteModelDataOnBoot;
+    }
+
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mDeleteResidencyDataOnBoot;
+    }
+
+    public PowerStatsLogger(Context context, File dataStoragePath,
+            String meterFilename, String meterCacheFilename,
+            String modelFilename, String modelCacheFilename,
+            String residencyFilename, String residencyCacheFilename,
             IPowerStatsHALWrapper powerStatsHALWrapper) {
         super(Looper.getMainLooper());
         mPowerStatsHALWrapper = powerStatsHALWrapper;
-        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mDataStoragePath = dataStoragePath;
+
+        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             meterFilename);
-        mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             modelFilename);
-        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath,
+        mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath,
             residencyFilename);
+
+        final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
+        final byte[] channelBytes = ChannelUtils.getProtoBytes(channels);
+        mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes);
+        if (mDeleteMeterDataOnBoot) {
+            mPowerStatsMeterStorage.deleteLogs();
+            updateCacheFile(meterCacheFilename, channelBytes);
+        }
+
+        final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo();
+        final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes);
+        if (mDeleteModelDataOnBoot) {
+            mPowerStatsModelStorage.deleteLogs();
+            updateCacheFile(modelCacheFilename, energyConsumerBytes);
+        }
+
+        final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo();
+        final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities);
+        mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes);
+        if (mDeleteResidencyDataOnBoot) {
+            mPowerStatsResidencyStorage.deleteLogs();
+            updateCacheFile(residencyCacheFilename, powerEntityBytes);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index b7285d5..bb52c1d 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -61,8 +61,12 @@
     private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
     private static final String RESIDENCY_FILENAME =
             "log.powerstats.residency." + DATA_STORAGE_VERSION;
+    private static final String METER_CACHE_FILENAME = "meterCache";
+    private static final String MODEL_CACHE_FILENAME = "modelCache";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
 
     private final Injector mInjector;
+    private File mDataStoragePath;
 
     private Context mContext;
     @Nullable
@@ -98,6 +102,18 @@
             return RESIDENCY_FILENAME;
         }
 
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
         IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
             return PowerStatsHALWrapper.getPowerStatsHalImpl();
         }
@@ -112,10 +128,15 @@
         }
 
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            return new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
         }
 
         BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@@ -187,14 +208,31 @@
         mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
+    @VisibleForTesting
+    public boolean getDeleteMeterDataOnBoot() {
+        return mPowerStatsLogger.getDeleteMeterDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteModelDataOnBoot() {
+        return mPowerStatsLogger.getDeleteModelDataOnBoot();
+    }
+
+    @VisibleForTesting
+    public boolean getDeleteResidencyDataOnBoot() {
+        return mPowerStatsLogger.getDeleteResidencyDataOnBoot();
+    }
+
     private void onBootCompleted() {
         if (getPowerStatsHal().isInitialized()) {
             if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers");
+            mDataStoragePath = mInjector.createDataStoragePath();
 
             // Only start logger and triggers if initialization is successful.
-            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
-                mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
-                mInjector.createModelFilename(), mInjector.createResidencyFilename(),
+            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
+                mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
+                mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
+                mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
                 getPowerStatsHal());
             mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
             mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index bd003d3..11b22a5 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -49,6 +49,12 @@
     private static final String TAG = ProtoStreamUtils.class.getSimpleName();
 
     static class PowerEntityUtils {
+        public static byte[] getProtoBytes(PowerEntity[] powerEntity) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(powerEntity, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(PowerEntity[] powerEntity,
                 ProtoOutputStream pos) {
             if (powerEntity == null) return;
@@ -260,6 +266,12 @@
     }
 
     static class ChannelUtils {
+        public static byte[] getProtoBytes(Channel[] channel) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(channel, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) {
             if (channel == null) return;
 
@@ -396,6 +408,12 @@
     }
 
     static class EnergyConsumerUtils {
+        public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) {
+            ProtoOutputStream pos = new ProtoOutputStream();
+            packProtoMessage(energyConsumer, pos);
+            return pos.getBytes();
+        }
+
         public static void packProtoMessage(EnergyConsumer[] energyConsumer,
                 ProtoOutputStream pos) {
             if (energyConsumer == null) return;
@@ -410,6 +428,72 @@
             }
         }
 
+        public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException {
+            final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
+            List<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>();
+
+            while (true) {
+                try {
+                    int nextField = pis.nextField();
+                    EnergyConsumer energyConsumer = new EnergyConsumer();
+
+                    if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) {
+                        long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER);
+                        energyConsumerList.add(unpackEnergyConsumerProto(pis));
+                        pis.end(token);
+                    } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
+                        return energyConsumerList.toArray(
+                            new EnergyConsumer[energyConsumerList.size()]);
+                    } else {
+                        Slog.e(TAG, "Unhandled field in proto: "
+                                + ProtoUtils.currentFieldToString(pis));
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in proto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
+        private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis)
+                throws IOException {
+            final EnergyConsumer energyConsumer = new EnergyConsumer();
+
+            while (true) {
+                try {
+                    switch (pis.nextField()) {
+                        case (int) EnergyConsumerProto.ID:
+                            energyConsumer.id = pis.readInt(EnergyConsumerProto.ID);
+                            break;
+
+                        case (int) EnergyConsumerProto.ORDINAL:
+                            energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL);
+                            break;
+
+                        case (int) EnergyConsumerProto.TYPE:
+                            energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE);
+                            break;
+
+                        case (int) EnergyConsumerProto.NAME:
+                            energyConsumer.name = pis.readString(EnergyConsumerProto.NAME);
+                            break;
+
+                        case ProtoInputStream.NO_MORE_FIELDS:
+                            return energyConsumer;
+
+                        default:
+                            Slog.e(TAG, "Unhandled field in EnergyConsumerProto: "
+                                    + ProtoUtils.currentFieldToString(pis));
+                            break;
+
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
         public static void print(EnergyConsumer[] energyConsumer) {
             if (energyConsumer == null) return;
 
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 0d43600..15c72b3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -139,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;
@@ -1455,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})
@@ -1649,17 +1650,18 @@
     }
 
     private void registerCpuCyclesPerThreadGroupCluster() {
-        // TODO(b/173227907): Register only when supported.
-        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
-        );
+        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) {
@@ -1718,7 +1720,7 @@
         }
         for (int cluster = 0; cluster < clusters; ++cluster) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    atomTag, threadGroup, cluster, aggregatedCycles[cluster],
+                    atomTag, threadGroup, cluster, aggregatedCycles[cluster] / 1_000_000L,
                     aggregatedTimesUs[cluster] / 1_000));
         }
     }
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3726407..6ad30b5 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -19,10 +19,12 @@
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
@@ -30,7 +32,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 
 import java.util.Collections;
@@ -86,18 +88,18 @@
     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
 
     /**
-     * Causes this VCN to immediately enter Safemode.
+     * Causes this VCN to immediately enter safe mode.
      *
-     * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
-     * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
+     * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
+     * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
      */
-    private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;
+    private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final Dependencies mDeps;
     @NonNull private final VcnNetworkRequestListener mRequestListener;
-    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
+    @NonNull private final VcnCallback mVcnCallback;
 
     @NonNull
     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,14 +127,8 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
-        this(
-                vcnContext,
-                subscriptionGroup,
-                config,
-                snapshot,
-                vcnSafemodeCallback,
-                new Dependencies());
+            @NonNull VcnCallback vcnCallback) {
+        this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -141,13 +137,12 @@
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
+            @NonNull VcnCallback vcnCallback,
             @NonNull Dependencies deps) {
         super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mVcnSafemodeCallback =
-                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
+        mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
         mRequestListener = new VcnNetworkRequestListener();
 
@@ -216,8 +211,8 @@
             case MSG_CMD_TEARDOWN:
                 handleTeardown();
                 break;
-            case MSG_CMD_ENTER_SAFEMODE:
-                handleEnterSafemode();
+            case MSG_CMD_ENTER_SAFE_MODE:
+                handleEnterSafeMode();
                 break;
             default:
                 Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
@@ -243,10 +238,10 @@
         mIsActive.set(false);
     }
 
-    private void handleEnterSafemode() {
+    private void handleEnterSafeMode() {
         handleTeardown();
 
-        mVcnSafemodeCallback.onEnteredSafemode();
+        mVcnCallback.onEnteredSafeMode();
     }
 
     private void handleNetworkRequested(
@@ -335,14 +330,31 @@
     /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public interface VcnGatewayStatusCallback {
-        /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
-        void onEnteredSafemode();
+        /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
+        void onEnteredSafeMode();
+
+        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
     }
 
     private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
         @Override
-        public void onEnteredSafemode() {
-            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
+        public void onEnteredSafeMode() {
+            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull int[] networkCapabilities,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            mVcnCallback.onGatewayConnectionError(
+                    networkCapabilities, errorCode, exceptionClass, exceptionMessage);
         }
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 37d13fb..9ee072e 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -22,11 +22,15 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
 
 import static com.android.server.VcnManagementService.VDBG;
 
 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 +41,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;
@@ -49,7 +55,9 @@
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
 import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnTransportInfo;
@@ -58,6 +66,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 +76,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,6 +132,15 @@
  * +----------------------------+
  * </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 {
@@ -128,6 +149,18 @@
     @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 int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -138,11 +171,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 {}
 
     /**
@@ -385,6 +422,23 @@
     // TODO(b/178426520): implement handling of this event
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
 
+    /**
+     * Sent when this VcnGatewayConnection has entered safe mode.
+     *
+     * <p>A VcnGatewayConnection enters safe mode 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_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
     final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -419,6 +473,21 @@
 
     @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;
 
@@ -481,7 +550,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,
@@ -518,6 +593,9 @@
 
         mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
 
+        mWakeLock =
+                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
                         mVcnContext,
@@ -544,7 +622,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));
@@ -560,6 +638,13 @@
             mTunnelIface.close();
         }
 
+        releaseWakeLock();
+
+        cancelTeardownTimeoutAlarm();
+        cancelDisconnectRequestAlarm();
+        cancelRetryTimeoutAlarm();
+        cancelSafeModeAlarm();
+
         mUnderlyingNetworkTracker.teardown();
     }
 
@@ -576,79 +661,377 @@
         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_SAFE_MODE_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_SAFE_MODE_TIMEOUT_EXCEEDED);
+    }
+
+    private void sessionLostWithoutCallback(int token, @Nullable Exception exception) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
     }
 
     private void sessionLost(int token, @Nullable Exception exception) {
-        sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+        // Only notify mGatewayStatusCallback if the session was lost with an error. All
+        // authentication and DNS failures are sent through
+        // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed()
+        if (exception != null) {
+            mGatewayStatusCallback.onGatewayConnectionError(
+                    mConnectionConfig.getRequiredUnderlyingCapabilities(),
+                    VCN_ERROR_CODE_INTERNAL_ERROR,
+                    "java.lang.RuntimeException",
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage());
+        }
+
+        sessionLostWithoutCallback(token, exception);
+    }
+
+    private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) {
+        final int errorCode;
+        final String exceptionClass;
+        final String exceptionMessage;
+
+        if (exception instanceof AuthenticationFailedException) {
+            errorCode = VCN_ERROR_CODE_CONFIG_ERROR;
+            exceptionClass = exception.getClass().getName();
+            exceptionMessage = exception.getMessage();
+        } else if (exception instanceof IkeInternalException
+                && exception.getCause() instanceof IOException) {
+            errorCode = VCN_ERROR_CODE_NETWORK_ERROR;
+            exceptionClass = "java.io.IOException";
+            exceptionMessage = exception.getCause().getMessage();
+        } else {
+            errorCode = VCN_ERROR_CODE_INTERNAL_ERROR;
+            exceptionClass = "java.lang.RuntimeException";
+            exceptionMessage =
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage();
+        }
+
+        mGatewayStatusCallback.onGatewayConnectionError(
+                mConnectionConfig.getRequiredUnderlyingCapabilities(),
+                errorCode,
+                exceptionClass,
+                exceptionMessage);
     }
 
     private void sessionClosed(int token, @Nullable Exception exception) {
+        if (exception != null) {
+            notifyStatusCallbackForSessionClosed(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);
+        sessionLostWithoutCallback(token, exception);
+        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 {
@@ -658,7 +1041,7 @@
                 enterState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -669,22 +1052,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;
         }
 
@@ -696,7 +1104,7 @@
                 exitState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -734,6 +1142,8 @@
         }
 
         protected void handleDisconnectRequested(String msg) {
+            // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
             Slog.v(TAG, "Tearing down. Cause: " + msg);
             mIsRunning = false;
 
@@ -774,6 +1184,8 @@
             if (mIkeSession != null || mNetworkAgent != null) {
                 Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
             }
+
+            cancelSafeModeAlarm();
         }
 
         @Override
@@ -797,27 +1209,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);
         }
@@ -848,7 +1249,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;
             }
 
@@ -860,10 +1261,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
@@ -888,6 +1288,8 @@
 
                     String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
                     if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                        // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
                         // Will trigger EVENT_SESSION_CLOSED immediately.
                         mIkeSession.kill();
                         break;
@@ -905,6 +1307,10 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -914,6 +1320,8 @@
         @Override
         protected void exitState() throws Exception {
             mSkipRetryTimeout = false;
+
+            cancelTeardownTimeoutAlarm();
         }
     }
 
@@ -985,6 +1393,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1028,6 +1440,14 @@
                         public void unwanted() {
                             teardownAsynchronously();
                         }
+
+                        @Override
+                        public void onValidationStatus(
+                                @ValidationStatus int status, @Nullable Uri redirectUri) {
+                            if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+                                clearFailedAttemptCounterAndSafeModeAlarm();
+                            }
+                        }
                     };
 
             agent.register();
@@ -1036,6 +1456,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,
@@ -1043,7 +1471,7 @@
                 @NonNull IpSecTransform transform,
                 int direction) {
             try {
-                // TODO: Set underlying network of tunnel interface
+                // TODO(b/180163196): Set underlying network of tunnel interface
 
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
@@ -1115,9 +1543,6 @@
                     teardownAsynchronously();
                 }
             }
-
-            // Successful connection, clear failed attempt counter
-            mFailedAttempts = 0;
         }
 
         @Override
@@ -1154,6 +1579,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafeMode();
+                    mSafeModeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1173,6 +1602,7 @@
             // mUnderlying assumed non-null, given check above.
             // If network changed, migrate. Otherwise, update any existing networkAgent.
             if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
+                Slog.v(TAG, "Migrating to new network: " + mUnderlying.network);
                 mIkeSession.setNetwork(mUnderlying.network);
             } else {
                 // oldUnderlying is non-null & underlying network itself has not changed
@@ -1196,8 +1626,18 @@
                 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() {
+            // Will only set a new alarm if no safe mode alarm is currently scheduled.
+            setSafeModeAlarm();
+        }
     }
 
     /**
@@ -1215,8 +1655,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());
             }
         }
 
@@ -1229,8 +1669,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
@@ -1241,19 +1679,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_SAFE_MODE_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();
@@ -1326,8 +1771,6 @@
             }
         }
 
-        // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs.
-
         return builder.build();
     }
 
@@ -1425,6 +1868,15 @@
         }
 
         @Override
+        public void onIpSecTransformsMigrated(
+                @NonNull IpSecTransform inIpSecTransform,
+                @NonNull IpSecTransform outIpSecTransform) {
+            Slog.v(TAG, "ChildTransformsMigrated; token " + mToken);
+            onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN);
+            onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT);
+        }
+
+        @Override
         public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
             // Nothing to be done; no references to the IpSecTransform are held, and this transform
             // will be closed by the IKE library.
@@ -1526,6 +1978,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();
+        }
     }
 
     /**
@@ -1605,4 +2077,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 04dac7c..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,7 +91,7 @@
     @GuardedBy("mLock")
     private boolean mForceStop;
 
-    public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
+    VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
             VibrationCallbacks callbacks) {
         mVibration = vib;
@@ -169,7 +168,7 @@
         }
     }
 
-    public Vibration getVibration() {
+    Vibration getVibration() {
         return mVibration;
     }
 
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 96%
rename from services/core/java/com/android/server/VibratorManagerService.java
rename to services/core/java/com/android/server/vibrator/VibratorManagerService.java
index d264f85..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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.vibrator;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -56,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;
 
@@ -356,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;
@@ -370,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);
                     }
@@ -495,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);
             }
@@ -599,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;
         }
@@ -967,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);
@@ -1167,6 +1160,7 @@
                     }
                 }
 
+                pw.println();
                 pw.println("  Previous external vibrations:");
                 for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
                     pw.println("    " + info);
@@ -1181,46 +1175,48 @@
                 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();
@@ -1300,7 +1296,7 @@
                     cancelingVibration.join();
                 } catch (InterruptedException e) {
                     Slog.w("Interrupted while waiting for vibration to finish before starting "
-                                    + "external control", e);
+                            + "external control", e);
                 }
             }
             if (DEBUG) {
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 79f8229..37fda4c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1997,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 f16a646..589a8c3 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;
 
@@ -317,6 +318,7 @@
     public static final String DUMP_CONTAINERS_CMD = "containers";
     public static final String DUMP_RECENTS_CMD = "recents";
     public static final String DUMP_RECENTS_SHORT_CMD = "r";
+    public static final String DUMP_TOP_RESUMED_ACTIVITY = "top-resumed";
 
     /** This activity is not being relaunched, or being relaunched for a non-resize reason. */
     public static final int RELAUNCH_REASON_NONE = 0;
@@ -451,6 +453,7 @@
     /** The controller for all operations related to locktask. */
     private LockTaskController mLockTaskController;
     private ActivityStartController mActivityStartController;
+    PackageConfigPersister mPackageConfigPersister;
 
     boolean mSuppressResizeConfigChanges;
 
@@ -866,6 +869,7 @@
         setRecentTasks(new RecentTasks(this, mTaskSupervisor));
         mVrController = new VrController(mGlobalLock);
         mKeyguardController = mTaskSupervisor.getKeyguardController();
+        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
     }
 
     public void onActivityManagerInternalAdded() {
@@ -2027,8 +2031,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 +3252,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
@@ -3730,6 +3757,14 @@
         }
     }
 
+    void dumpTopResumedActivityLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER TOP-RESUMED (dumpsys activity top-resumed)");
+        ActivityRecord topRecord = mRootWindowContainer.getTopResumedActivity();
+        if (topRecord != null) {
+            topRecord.dump(pw, "", true);
+        }
+    }
+
     void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
@@ -5433,6 +5468,7 @@
             synchronized (mGlobalLock) {
                 mAppWarnings.onPackageUninstalled(name);
                 mCompatModePackages.handlePackageUninstalledLocked(name);
+                mPackageConfigPersister.onPackageUninstall(name);
             }
         }
 
@@ -5869,6 +5905,8 @@
                     if (getRecentTasks() != null) {
                         getRecentTasks().dump(pw, dumpAll, dumpPackage);
                     }
+                } else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
+                    dumpTopResumedActivityLocked(pw);
                 }
             }
         }
@@ -6103,6 +6141,7 @@
         public void removeUser(int userId) {
             synchronized (mGlobalLock) {
                 mRootWindowContainer.removeUser(userId);
+                mPackageConfigPersister.removeUser(userId);
             }
         }
 
@@ -6200,6 +6239,8 @@
         public void loadRecentTasksForUser(int userId) {
             synchronized (mGlobalLock) {
                 mRecentTasks.loadUserRecentsLocked(userId);
+                // TODO renaming the methods(?)
+                mPackageConfigPersister.loadUserPackages(userId);
             }
         }
 
@@ -6308,5 +6349,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e51d690..86968ed 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -240,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;
 
@@ -663,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();
@@ -5553,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/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
new file mode 100644
index 0000000..5d6d513
--- /dev/null
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -0,0 +1,143 @@
+/*
+ * 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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
+import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class NonAppWindowAnimationAdapter implements AnimationAdapter {
+
+    private final WindowState mWindow;
+    private RemoteAnimationTarget mTarget;
+    private SurfaceControl mCapturedLeash;
+
+    private long mDurationHint;
+    private long mStatusBarTransitionDelay;
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    NonAppWindowAnimationAdapter(WindowState w,
+            long durationHint, long statusBarTransitionDelay) {
+        mWindow = w;
+        mDurationHint = durationHint;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+    }
+
+    /**
+     * Creates and starts remote animations for all the visible non app windows.
+     *
+     * @return RemoteAnimationTarget[] targets for all the visible non app windows
+     */
+    public static RemoteAnimationTarget[] startNonAppWindowAnimationsForKeyguardExit(
+            WindowManagerService service, long durationHint, long statusBarTransitionDelay) {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+
+        final WindowManagerPolicy policy = service.mPolicy;
+        service.mRoot.forAllWindows(nonAppWindow -> {
+            if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow)
+                    && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()) {
+                final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
+                        nonAppWindow, durationHint, statusBarTransitionDelay);
+                nonAppWindow.startAnimation(nonAppWindow.getPendingTransaction(),
+                        nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+                targets.add(nonAppAdapter.createRemoteAnimationTarget());
+            }
+        }, true /* traverseTopToBottom */);
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    /**
+     * Create a remote animation target for this animation adapter.
+     */
+    RemoteAnimationTarget createRemoteAnimationTarget() {
+        mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
+                new Rect(), null, mWindow.getPrefixOrderIndex(), mWindow.getLastSurfacePosition(),
+                mWindow.getBounds(), null, mWindow.getWindowConfiguration(), true, null, null,
+                null);
+        return mTarget;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        mCapturedLeash = animationLeash;
+    }
+
+    @Override
+    public long getDurationHint() {
+        return mDurationHint;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
+    }
+
+    /**
+     * @return the leash for this animation (only valid after the non app window surface animation
+     * has started).
+     */
+    SurfaceControl getLeash() {
+        return mCapturedLeash;
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationCancelled");
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("token=");
+        pw.println(mWindow.mToken);
+        if (mTarget != null) {
+            pw.print(prefix);
+            pw.println("Target:");
+            mTarget.dump(pw, prefix + "  ");
+        } else {
+            pw.print(prefix);
+            pw.println("Target: null");
+        }
+    }
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto) {
+        final long token = proto.start(REMOTE);
+        if (mTarget != null) {
+            mTarget.dumpDebug(proto, TARGET);
+        }
+        proto.end(token);
+    }
+}
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/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 392f27e..42cb96f 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -126,7 +129,7 @@
         final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
 
         // TODO(bc-unlock): Create the remote non app animation targets (if any)
-        final RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0];
+        final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
 
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
@@ -215,6 +218,17 @@
                 }, mPendingWallpaperAnimations);
     }
 
+    private RemoteAnimationTarget[] createNonAppWindowAnimations(
+            @WindowManager.TransitionOldType int transit) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
+        return (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
+                ? NonAppWindowAnimationAdapter.startNonAppWindowAnimationsForKeyguardExit(mService,
+                    mRemoteAnimationAdapter.getDuration(),
+                    mRemoteAnimationAdapter.getStatusBarTransitionDelay())
+                : new RemoteAnimationTarget[0];
+    }
+
     private void onAnimationFinished() {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d",
                 mPendingAnimations.size());
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ceebe95..bb5e8bf 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,7 +847,6 @@
         mWmService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction();
-            mWmService.mSyncEngine.onSurfacePlacement();
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
@@ -861,6 +860,7 @@
 
         // Send any pending task-info changes that were queued-up during a layout deferment
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        mWmService.mSyncEngine.onSurfacePlacement();
         mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
@@ -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/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 9c8a997..d360916 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;
@@ -3881,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()
@@ -4267,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) {
@@ -5290,7 +5301,8 @@
      */
     void onWindowFocusChanged(boolean hasFocus) {
         updateShadowsRadius(hasFocus, getSyncTransaction());
-        dispatchTaskInfoChangedIfNeeded(false /* force */);
+        // TODO(b/180525887): Un-comment once there is resolution on the bug.
+        // dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
@@ -5844,12 +5856,8 @@
             mTaskSupervisor.acquireLaunchWakelock();
         }
 
-        if (didAutoPip) {
-            // Already entered PIP mode, no need to keep pausing.
-            return true;
-        }
-
-        if (mPausingActivity != null) {
+        // If already entered PIP mode, no need to keep pausing.
+        if (mPausingActivity != null && !didAutoPip) {
             // Have the window manager pause its key dispatching until the new
             // activity has started.  If we're pausing the activity just because
             // the screen is being turned off and the UI is sleeping, don't interrupt
@@ -5872,9 +5880,9 @@
             }
 
         } else {
-            // This activity failed to schedule the
-            // pause, so just treat it as being paused now.
-            ProtoLog.v(WM_DEBUG_STATES, "Activity not running, resuming next.");
+            // This activity either failed to schedule the pause or it entered PIP mode,
+            // so just treat it as being paused now.
+            ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
             if (resuming == null) {
                 mRootWindowContainer.resumeFocusedTasksTopActivities();
             }
@@ -6558,10 +6566,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;
@@ -6584,8 +6591,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);
             }
@@ -6746,7 +6752,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,
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/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/jni/Android.bp b/services/core/jni/Android.bp
index a7abf6a..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,7 +55,7 @@
         "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_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.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/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 a6029cd..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,7 +73,8 @@
               static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
-    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..48ae8d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -138,9 +138,12 @@
     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_NETWORK_SLICING_ENABLED = "network-slicing-enabled";
+    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";
+    private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true;
 
     DeviceAdminInfo info;
 
@@ -280,6 +283,10 @@
     public String mOrganizationId;
     public String mEnrollmentSpecificId;
     public boolean mAdminCanGrantSensorsPermissions;
+    public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT;
+
+    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;
@@ -548,6 +555,12 @@
         }
         writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
                 mAdminCanGrantSensorsPermissions);
+        if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled);
+        }
+        if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
+        }
     }
 
     void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -777,6 +790,9 @@
                 mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
             } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
                 mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
+            } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) {
+                mNetworkSlicingEnabled = parser.getAttributeBoolean(
+                        null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT);
             } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
                 mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
             } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
@@ -800,6 +816,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);
@@ -1136,6 +1155,9 @@
         pw.print("mAlwaysOnVpnLockdown=");
         pw.println(mAlwaysOnVpnLockdown);
 
+        pw.print("mNetworkSlicingEnabled=");
+        pw.println(mNetworkSlicingEnabled);
+
         pw.print("mCommonCriteriaMode=");
         pw.println(mCommonCriteriaMode);
 
@@ -1154,5 +1176,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 9a692be7..1cf4ce1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -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;
@@ -246,6 +247,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.permission.AdminPermissionControlParams;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.provider.CalendarContract;
@@ -421,10 +423,12 @@
         DELEGATION_CERT_SELECTION,
     };
 
-    // Subset of delegations that can only be delegated by Device Owner.
-    private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
-            DELEGATION_NETWORK_LOGGING,
-    });
+    // Subset of delegations that can only be delegated by Device Owner or Profile Owner of a
+    // managed profile.
+    private static final List<String> DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS =
+            Arrays.asList(new String[]{
+                    DELEGATION_NETWORK_LOGGING,
+            });
 
     // Subset of delegations that only one single package within a given user can hold
     private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
@@ -530,8 +534,10 @@
 
     /**
      * Strings logged with {@link
-     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
-     * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
+     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
+     * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
+     * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
      */
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -781,8 +787,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);
@@ -1357,6 +1362,10 @@
             return mContext.getSystemService(WifiManager.class);
         }
 
+        UsbManager getUsbManager() {
+            return mContext.getSystemService(UsbManager.class);
+        }
+
         @SuppressWarnings("AndroidFrameworkBinderIdentity")
         long binderClearCallingIdentity() {
             return Binder.clearCallingIdentity();
@@ -2928,6 +2937,7 @@
 
             revertTransferOwnershipIfNecessaryLocked();
         }
+        updateUsbDataSignal();
     }
 
     private void revertTransferOwnershipIfNecessaryLocked() {
@@ -5879,10 +5889,10 @@
         }
         // Retrieve the user ID of the calling process.
         final int userId = caller.getUserId();
-        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         // Ensure calling process is device/profile owner.
-        if (hasDoDelegation) {
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
+            Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                    || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
         } else {
             Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
         }
@@ -6443,7 +6453,7 @@
     private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
         boolean success = false;
         try {
-            if (getCurrentForegroundUser() == userId) {
+            if (getCurrentForegroundUserId() == userId) {
                 mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
             }
 
@@ -7527,6 +7537,20 @@
         sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
     }
 
+    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));
@@ -7870,6 +7894,14 @@
             updateDeviceOwnerLocked();
             setDeviceOwnershipSystemPropertyLocked();
 
+            //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this
+            // hard-coded default value setting.
+            if (isAdb(caller)) {
+                activeAdmin.mAdminCanGrantSensorsPermissions = true;
+                mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true);
+                saveSettingsLocked(userId);
+            }
+
             mInjector.binderWithCleanCallingIdentity(() -> {
                 // Restrict adding a managed profile when a device owner is set on the device.
                 // That is to prevent the co-existence of a managed profile and a device owner
@@ -7889,7 +7921,7 @@
             Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
 
             if (mInjector.userManagerIsHeadlessSystemUserMode()) {
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 Slog.i(LOG_TAG, "setDeviceOwner(): setting " + admin
                         + " as profile owner on user " + currentForegroundUser);
                 // Sets profile owner on current foreground user since
@@ -8351,6 +8383,7 @@
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
         applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        setNetworkLoggingActiveInternal(false);
     }
 
     @Override
@@ -9019,7 +9052,7 @@
         return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
     }
 
-    private @UserIdInt int getCurrentForegroundUser() {
+    private @UserIdInt int getCurrentForegroundUserId() {
         try {
             return mInjector.getIActivityManager().getCurrentUser().id;
         } catch (RemoteException e) {
@@ -9028,6 +9061,25 @@
         return UserHandle.USER_NULL;
     }
 
+    @Override
+    public List<UserHandle> listForegroundAffiliatedUsers() {
+        checkIsDeviceOwner(getCallerIdentity());
+
+        int userId = mInjector.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId());
+
+        boolean isAffiliated;
+        synchronized (getLockObject()) {
+            isAffiliated = isUserAffiliatedWithDeviceLocked(userId);
+        }
+
+        if (!isAffiliated) return Collections.emptyList();
+
+        List<UserHandle> users = new ArrayList<>(1);
+        users.add(UserHandle.of(userId));
+
+        return users;
+    }
+
     protected int getProfileParentId(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -11112,6 +11164,50 @@
     }
 
     @Override
+    public void setNetworkSlicingEnabled(boolean enabled) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner; only profile owner may control the network slicing");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
+                    caller.getUserId());
+            if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
+                requiredAdmin.mNetworkSlicingEnabled = enabled;
+                saveSettingsLocked(caller.getUserId());
+                // TODO(b/178655595) notify CS the change.
+                // TODO(b/178655595) DevicePolicyEventLogger metrics
+            }
+        }
+    }
+
+    @Override
+    public boolean isNetworkSlicingEnabled(int userHandle) {
+        if (!mHasFeature) {
+            return false;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+                permission.READ_NETWORK_DEVICE_CONFIG) || isProfileOwner(caller),
+                        "Caller is not profile owner and not granted"
+                                + " READ_NETWORK_DEVICE_CONFIG permission");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
+            if (requiredAdmin != null) {
+                return requiredAdmin.mNetworkSlicingEnabled;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    @Override
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -12621,9 +12717,12 @@
                 if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                    AdminPermissionControlParams permissionParams =
+                            new AdminPermissionControlParams(packageName, permission, grantState,
+                                    canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
                             .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
-                                    packageName, permission, grantState, mContext.getMainExecutor(),
+                                    permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
                                             callback.sendResult(null);
@@ -12839,7 +12938,7 @@
                     return CODE_NONSYSTEM_USER_EXISTS;
                 }
 
-                int currentForegroundUser = getCurrentForegroundUser();
+                int currentForegroundUser = getCurrentForegroundUserId();
                 if (callingUserId != currentForegroundUser
                         && mInjector.userManagerIsHeadlessSystemUserMode()
                         && currentForegroundUser == UserHandle.USER_SYSTEM) {
@@ -12935,6 +13034,11 @@
         return CODE_OK;
     }
 
+    private void checkIsDeviceOwner(CallerIdentity caller) {
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+                + " is not device owner");
+    }
+
     private ComponentName getOwnerComponent(String packageName, int userId) {
         if (isDeviceOwnerPackage(packageName, userId)) {
             return mOwners.getDeviceOwnerComponent();
@@ -14131,7 +14235,10 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14139,11 +14246,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);
@@ -14153,6 +14260,8 @@
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
                     .setInt(enabled ? 1 : 0)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .write();
         }
     }
@@ -14161,7 +14270,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"
@@ -14181,6 +14296,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()),
@@ -14201,10 +14335,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 "
@@ -14223,7 +14359,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();
                 }
@@ -14250,7 +14388,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));
 
@@ -14260,8 +14400,8 @@
     }
 
     private boolean isNetworkLoggingEnabledInternalLocked() {
-        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+        ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
     }
 
     /*
@@ -14278,9 +14418,14 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        final boolean isManagedProfileOwner = isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId());
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller) || isManagedProfileOwner))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
-        checkAllUsersAreAffiliatedWithDevice();
+        if (mOwners.hasDeviceOwner()) {
+            checkAllUsersAreAffiliatedWithDevice();
+        }
 
         synchronized (getLockObject()) {
             if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) {
@@ -14290,37 +14435,40 @@
                     .createEvent(DevicePolicyEnums.RETRIEVE_NETWORK_LOGS)
                     .setAdmin(caller.getPackageName())
                     .setBoolean(/* isDelegate */ admin == null)
+                    .setStrings(isManagedProfileOwner
+                            ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
                     .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);
@@ -14340,7 +14488,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());
     }
 
     /**
@@ -14404,8 +14552,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
@@ -15383,7 +15535,7 @@
     private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException {
         //TODO(b/175285301): Explicitly get the user's identity to check.
         int lockTaskFeatures =
-                getUserData(getCurrentForegroundUser()).mLockTaskFeatures;
+                getUserData(getCurrentForegroundUserId()).mLockTaskFeatures;
         return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
     }
 
@@ -16567,4 +16719,80 @@
 
         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() != null
+                        && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3
+        );
+    }
 }
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/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 3f47227..2b09d12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -138,6 +138,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.location.LocationManagerService;
 import com.android.server.media.MediaRouterService;
+import com.android.server.media.metrics.MediaMetricsManagerService;
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
@@ -195,6 +196,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;
@@ -252,6 +254,10 @@
             "com.android.server.companion.CompanionDeviceManagerService";
     private static final String STATS_COMPANION_APEX_PATH =
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
+    private static final String SCHEDULING_APEX_PATH =
+            "/apex/com.android.scheduling/javalib/service-scheduling.jar";
+    private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
+            "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
     private static final String CONNECTIVITY_SERVICE_APEX_PATH =
             "/apex/com.android.tethering/javalib/service-connectivity.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
@@ -2391,6 +2397,10 @@
             t.traceBegin("StartPeopleService");
             mSystemServiceManager.startService(PeopleService.class);
             t.traceEnd();
+
+            t.traceBegin("StartMediaMetricsManager");
+            mSystemServiceManager.startService(MediaMetricsManagerService.class);
+            t.traceEnd();
         }
 
         if (!isWatch) {
@@ -2446,6 +2456,12 @@
                 STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
         t.traceEnd();
 
+        // Reboot Readiness
+        t.traceBegin("StartRebootReadinessManagerService");
+        mSystemServiceManager.startServiceFromJar(
+                REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH);
+        t.traceEnd();
+
         // Statsd pulled atoms
         t.traceBegin("StartStatsPullAtomService");
         mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 5453de1..e7d0121 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -16,12 +16,14 @@
 
 package com.android.server.people;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.people.ConversationChannel;
 import android.app.people.ConversationStatus;
+import android.app.people.IConversationListener;
 import android.app.people.IPeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
@@ -29,12 +31,15 @@
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -45,6 +50,7 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -56,7 +62,9 @@
 
     private static final String TAG = "PeopleService";
 
-    private final DataManager mDataManager;
+    private DataManager mDataManager;
+    @VisibleForTesting
+    ConversationListenerHelper mConversationListenerHelper;
 
     private PackageManagerInternal mPackageManagerInternal;
 
@@ -69,6 +77,8 @@
         super(context);
 
         mDataManager = new DataManager(context);
+        mConversationListenerHelper = new ConversationListenerHelper();
+        mDataManager.addConversationsListener(mConversationListenerHelper);
     }
 
     @Override
@@ -146,12 +156,14 @@
      * @param message used as message if SecurityException is thrown
      * @throws SecurityException if the caller is not system or root
      */
-    private static void enforceSystemRootOrSystemUI(Context context, String message) {
+    @VisibleForTesting
+    protected void enforceSystemRootOrSystemUI(Context context, String message) {
         if (isSystemOrRoot()) return;
         context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
                 message);
     }
 
+    @VisibleForTesting
     final IBinder mService = new IPeopleManager.Stub() {
 
         @Override
@@ -184,6 +196,20 @@
         }
 
         @Override
+        public boolean isConversation(String packageName, int userId, String shortcutId) {
+            enforceHasReadPeopleDataPermission();
+            handleIncomingUser(userId);
+            return mDataManager.isConversation(packageName, userId, shortcutId);
+        }
+
+        private void enforceHasReadPeopleDataPermission() throws SecurityException {
+            if (getContext().checkCallingPermission(Manifest.permission.READ_PEOPLE_DATA)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Caller doesn't have READ_PEOPLE_DATA permission.");
+            }
+        }
+
+        @Override
         public long getLastInteraction(String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get last interaction");
             return mDataManager.getLastInteraction(packageName, userId, shortcutId);
@@ -225,8 +251,137 @@
             return new ParceledListSlice<>(
                     mDataManager.getStatuses(packageName, userId, conversationId));
         }
+
+        @Override
+        public void registerConversationListener(
+                String packageName, int userId, String shortcutId, IConversationListener listener) {
+            enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
+            mConversationListenerHelper.addConversationListener(
+                    new ListenerKey(packageName, userId, shortcutId), listener);
+        }
+
+        @Override
+        public void unregisterConversationListener(IConversationListener listener) {
+            enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
+            mConversationListenerHelper.removeConversationListener(listener);
+        }
     };
 
+    /**
+     * Listeners for conversation changes.
+     *
+     * @hide
+     */
+    public interface ConversationsListener {
+        /**
+         * Triggers with the list of modified conversations from {@link DataManager} for dispatching
+         * relevant updates to clients.
+         *
+         * @param conversations The conversations with modified data
+         * @see IPeopleManager#registerConversationListener(String, int, String,
+         * android.app.people.ConversationListener)
+         */
+        default void onConversationsUpdate(@NonNull List<ConversationChannel> conversations) {
+        }
+    }
+
+    /**
+     * Implements {@code ConversationListenerHelper} to dispatch conversation updates to registered
+     * clients.
+     */
+    public static class ConversationListenerHelper implements ConversationsListener {
+
+        ConversationListenerHelper() {
+        }
+
+        @VisibleForTesting
+        final RemoteCallbackList<IConversationListener> mListeners =
+                new RemoteCallbackList<>();
+
+        /** Adds {@code listener} with {@code key} associated. */
+        public synchronized void addConversationListener(ListenerKey key,
+                IConversationListener listener) {
+            mListeners.unregister(listener);
+            mListeners.register(listener, key);
+        }
+
+        /** Removes {@code listener}. */
+        public synchronized void removeConversationListener(
+                IConversationListener listener) {
+            mListeners.unregister(listener);
+        }
+
+        @Override
+        /** Dispatches updates to {@code mListeners} with keys mapped to {@code conversations}. */
+        public void onConversationsUpdate(List<ConversationChannel> conversations) {
+            int count = mListeners.beginBroadcast();
+            // Early opt-out if no listeners are registered.
+            if (count == 0) {
+                return;
+            }
+            Map<ListenerKey, ConversationChannel> keyedConversations = new HashMap<>();
+            for (ConversationChannel conversation : conversations) {
+                keyedConversations.put(getListenerKey(conversation), conversation);
+            }
+            for (int i = 0; i < count; i++) {
+                final ListenerKey listenerKey = (ListenerKey) mListeners.getBroadcastCookie(i);
+                if (!keyedConversations.containsKey(listenerKey)) {
+                    continue;
+                }
+                final IConversationListener listener = mListeners.getBroadcastItem(i);
+                try {
+                    ConversationChannel channel = keyedConversations.get(listenerKey);
+                    listener.onConversationUpdate(channel);
+                } catch (RemoteException e) {
+                    // The RemoteCallbackList will take care of removing the dead object.
+                }
+            }
+            mListeners.finishBroadcast();
+        }
+
+        private ListenerKey getListenerKey(ConversationChannel conversation) {
+            ShortcutInfo info = conversation.getShortcutInfo();
+            return new ListenerKey(info.getPackage(), info.getUserId(),
+                    info.getId());
+        }
+    }
+
+    private static class ListenerKey {
+        private final String mPackageName;
+        private final Integer mUserId;
+        private final String mShortcutId;
+
+        ListenerKey(String packageName, Integer userId, String shortcutId) {
+            this.mPackageName = packageName;
+            this.mUserId = userId;
+            this.mShortcutId = shortcutId;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public Integer getUserId() {
+            return mUserId;
+        }
+
+        public String getShortcutId() {
+            return mShortcutId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            ListenerKey key = (ListenerKey) o;
+            return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+                    && key.getShortcutId().equals(mShortcutId);
+        }
+
+        @Override
+        public int hashCode() {
+            return mPackageName.hashCode() + mUserId.hashCode() + mShortcutId.hashCode();
+        }
+    }
+
     @VisibleForTesting
     final class LocalService extends PeopleServiceInternal {
 
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 444f9c6..75614d6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -48,6 +48,7 @@
 import android.net.Uri;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -58,6 +59,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -74,14 +76,17 @@
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.notification.ShortcutHelper;
+import com.android.server.people.PeopleService;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -117,6 +122,11 @@
     private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
     private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
     private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final List<PeopleService.ConversationsListener> mConversationsListeners =
+            new ArrayList<>(1);
+    private final Handler mHandler;
+
     private ContentObserver mCallLogContentObserver;
     private ContentObserver mMmsSmsContentObserver;
 
@@ -127,14 +137,14 @@
     private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
 
     public DataManager(Context context) {
-        this(context, new Injector());
+        this(context, new Injector(), BackgroundThread.get().getLooper());
     }
 
-    @VisibleForTesting
-    DataManager(Context context, Injector injector) {
+    DataManager(Context context, Injector injector, Looper looper) {
         mContext = context;
         mInjector = injector;
         mScheduledExecutor = mInjector.createScheduledExecutor();
+        mHandler = new Handler(looper);
     }
 
     /** Initialization. Called when the system services are up running. */
@@ -233,20 +243,19 @@
             PackageData packageData = userData.getPackageData(packageName);
             // App may have been uninstalled.
             if (packageData != null) {
-                return getConversationChannel(packageData, shortcutId);
+                ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+                return getConversationChannel(packageName, userId, shortcutId, conversationInfo);
             }
         }
         return null;
     }
 
     @Nullable
-    private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
-        ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+    private ConversationChannel getConversationChannel(String packageName, int userId,
+            String shortcutId, ConversationInfo conversationInfo) {
         if (conversationInfo == null) {
             return null;
         }
-        int userId = packageData.getUserId();
-        String packageName = packageData.getPackageName();
         ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
         if (shortcutInfo == null) {
             return null;
@@ -277,7 +286,8 @@
                     return;
                 }
                 String shortcutId = conversationInfo.getShortcutId();
-                ConversationChannel channel = getConversationChannel(packageData, shortcutId);
+                ConversationChannel channel = getConversationChannel(packageData.getPackageName(),
+                        packageData.getUserId(), shortcutId, conversationInfo);
                 if (channel == null || channel.getParentNotificationChannel() == null) {
                     return;
                 }
@@ -354,6 +364,14 @@
         });
     }
 
+    /** Returns whether {@code shortcutId} is backed by Conversation. */
+    public boolean isConversation(String packageName, int userId, String shortcutId) {
+        ConversationChannel channel = getConversation(packageName, userId, shortcutId);
+        return channel != null
+                && channel.getShortcutInfo() != null
+                && !TextUtils.isEmpty(channel.getShortcutInfo().getLabel());
+    }
+
     /**
      * Returns the last notification interaction with the specified conversation. If the
      * conversation can't be found or no interactions have been recorded, returns 0L.
@@ -375,7 +393,11 @@
         ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
         ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
         builder.addOrUpdateStatus(status);
-        cs.addOrUpdate(builder.build());
+        ConversationInfo modifiedConv = builder.build();
+        cs.addOrUpdate(modifiedConv);
+        ConversationChannel conversation = getConversationChannel(packageName, userId,
+                conversationId, modifiedConv);
+        notifyConversationsListeners(Arrays.asList(conversation));
 
         if (status.getEndTimeMillis() >= 0) {
             mStatusExpReceiver.scheduleExpiration(
@@ -1226,6 +1248,32 @@
         }
     }
 
+    /** Adds {@code listener} to be notified on conversation changes. */
+    public void addConversationsListener(
+            @NonNull PeopleService.ConversationsListener listener) {
+        synchronized (mConversationsListeners) {
+            mConversationsListeners.add(Objects.requireNonNull(listener));
+        }
+    }
+
+    // TODO(b/178792356): Trigger ConversationsListener on all-related data changes.
+    @VisibleForTesting
+    void notifyConversationsListeners(
+            @Nullable final List<ConversationChannel> changedConversations) {
+        mHandler.post(() -> {
+            try {
+                final List<PeopleService.ConversationsListener> copy;
+                synchronized (mLock) {
+                    copy = new ArrayList<>(mConversationsListeners);
+                }
+                for (PeopleService.ConversationsListener listener : copy) {
+                    listener.onConversationsUpdate(changedConversations);
+                }
+            } catch (Exception e) {
+            }
+        });
+    }
+
     /** A {@link BroadcastReceiver} that receives the intents for a specified user. */
     private class PerUserBroadcastReceiver extends BroadcastReceiver {
 
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 c2e0b77..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
@@ -334,6 +334,7 @@
                     this[0] = PackageUserState()
                 }
             }
+            whenever(getInstantApp(anyInt())) { false }
         }
     }
 
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 5629d1c..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 }
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 1254df9..9109881 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -18,6 +18,7 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
 import static android.app.AlarmManager.FLAG_STANDALONE;
@@ -25,11 +26,14 @@
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.AlarmManager.WINDOW_HEURISTIC;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -44,7 +48,7 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
-import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
@@ -61,6 +65,7 @@
 import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -75,20 +80,29 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
+import android.app.IAlarmManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.os.BatteryManager;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -102,9 +116,12 @@
 
 import com.android.dx.mockito.inline.extended.MockedVoidMethod;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
+import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.usage.AppStandbyInternal;
@@ -125,6 +142,7 @@
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.LongConsumer;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -134,14 +152,21 @@
     private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
 
     private long mAppStandbyWindow;
+    private long mAllowWhileIdleWindow;
     private AlarmManagerService mService;
     private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener;
     private AlarmManagerService.ChargingReceiver mChargingReceiver;
+    private IAppOpsCallback mIAppOpsCallback;
+    private IAlarmManager mBinder;
     @Mock
     private Context mMockContext;
     @Mock
     private IActivityManager mIActivityManager;
     @Mock
+    private IAppOpsService mIAppOpsService;
+    @Mock
+    private DeviceIdleInternal mDeviceIdleInternal;
+    @Mock
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private AppStandbyInternal mAppStandbyInternal;
@@ -280,6 +305,11 @@
             // "IllegalStateException: Querying activity state off main thread is not allowed."
             // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener().
         }
+
+        @Override
+        IAppOpsService getAppOpsService() {
+            return mIAppOpsService;
+        }
     }
 
     @Before
@@ -287,15 +317,20 @@
         mMockingSession = mockitoSession()
                 .initMocks(this)
                 .spyStatic(ActivityManager.class)
+                .mockStatic(CompatChanges.class)
                 .spyStatic(DeviceConfig.class)
                 .mockStatic(LocalServices.class)
                 .spyStatic(Looper.class)
+                .mockStatic(PermissionChecker.class)
                 .mockStatic(Settings.Global.class)
                 .mockStatic(ServiceManager.class)
                 .spyStatic(UserHandle.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
+
         doReturn(mIActivityManager).when(ActivityManager::getService);
+        doReturn(mDeviceIdleInternal).when(
+                () -> LocalServices.getService(DeviceIdleInternal.class));
         doReturn(mActivityManagerInternal).when(
                 () -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -330,6 +365,9 @@
                 () -> DeviceConfig.getProperties(
                         eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any()));
 
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(
+                mock(AppOpsManager.class));
+
         mInjector = new Injector(mMockContext);
         mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
@@ -346,6 +384,7 @@
         // Other boot phases don't matter
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+        mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW;
         ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor =
                 ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
         verify(mAppStandbyInternal).addListener(captor.capture());
@@ -358,6 +397,20 @@
                         && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
         mChargingReceiver = chargingReceiverCaptor.getValue();
 
+        ArgumentCaptor<IBinder> binderCaptor = ArgumentCaptor.forClass(IBinder.class);
+        verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(),
+                anyBoolean(), anyInt()));
+        mBinder = IAlarmManager.Stub.asInterface(binderCaptor.getValue());
+
+        ArgumentCaptor<IAppOpsCallback> appOpsCallbackCaptor = ArgumentCaptor.forClass(
+                IAppOpsCallback.class);
+        try {
+            verify(mIAppOpsService).startWatchingMode(eq(AppOpsManager.OP_SCHEDULE_EXACT_ALARM),
+                    isNull(), appOpsCallbackCaptor.capture());
+        } catch (RemoteException e) {
+            // Not expected on a mock.
+        }
+        mIAppOpsCallback = appOpsCallbackCaptor.getValue();
         setTestableQuotas();
     }
 
@@ -389,13 +442,18 @@
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
             int flags, int callingUid) {
+        setTestAlarm(type, triggerTime, operation, interval, flags, callingUid, null);
+    }
+
+    private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
+            int flags, int callingUid, Bundle idleOptions) {
         mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags,
-                null, null, callingUid, TEST_CALLING_PACKAGE);
+                null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions);
     }
 
     private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
         mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
-                FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+                FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
     }
 
 
@@ -506,14 +564,27 @@
         setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
         setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
         setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 25);
         setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
         setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
         assertEquals(5, mService.mConstants.MIN_FUTURITY);
         assertEquals(10, mService.mConstants.MIN_INTERVAL);
         assertEquals(15, mService.mConstants.MAX_INTERVAL);
         assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+        assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
         assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
         assertEquals(35, mService.mConstants.LISTENER_TIMEOUT);
+
+        // Test safeguards.
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 0);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
+
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, -8);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 0);
+        assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
     }
 
     @Test
@@ -559,56 +630,45 @@
         assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed());
     }
 
-    private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
         }
         // This one should get deferred on set
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent());
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
+        alarmSetter.accept(firstTrigger + quota);
+        final long expectedNextTrigger = firstTrigger + window;
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
-    private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
         }
-        // This one should get deferred after the latest alarm expires
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent());
+        // This one should get deferred after the latest alarm expires.
+        alarmSetter.accept(firstTrigger + quota);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
         }
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
+        final long expectedNextTrigger = firstTrigger + window;
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
-    private void testQuotasNoDeferral(int standbyBucket) throws Exception {
-        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
-        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
-                anyLong())).thenReturn(standbyBucket);
+    private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window)
+            throws Exception {
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
-            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
-                    getNewMockPendingIntent());
+            alarmSetter.accept(firstTrigger + i);
         }
         // This delivery time maintains the quota invariant. Should not be deferred.
-        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+        final long expectedNextTrigger = firstTrigger + window + 5;
+        alarmSetter.accept(expectedNextTrigger);
         for (int i = 0; i < quota; i++) {
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
@@ -616,64 +676,88 @@
         assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
+    private void testStandbyQuotasDeferralOnSet(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
+    private void testStandbyQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
+    private void testStandbyQuotasNoDeferral(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent()), quota, mAppStandbyWindow);
+    }
+
     @Test
     public void testActiveQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testActiveQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testActiveQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
     }
 
     @Test
     public void testWorkingQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testWorkingQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testWorkingQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
     }
 
     @Test
     public void testFrequentQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testFrequentQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testFrequentQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
     }
 
     @Test
     public void testRareQuota_deferredOnSet() throws Exception {
-        testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+        testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
     }
 
     @Test
     public void testRareQuota_deferredOnExpiration() throws Exception {
-        testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+        testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
     }
 
     @Test
     public void testRareQuota_notDeferred() throws Exception {
-        testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+        testStandbyQuotasNoDeferral(STANDBY_BUCKET_RARE);
     }
 
     @Test
@@ -731,7 +815,7 @@
     }
 
     @Test
-    public void testQuotaDowngrade() throws Exception {
+    public void testStandbyQuotaDowngrade() throws Exception {
         final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
                 anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
@@ -758,7 +842,7 @@
     }
 
     @Test
-    public void testQuotaUpgrade() throws Exception {
+    public void testStandbyQuotaUpgrade() throws Exception {
         final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
                 anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
@@ -1308,7 +1392,7 @@
     public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000,
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
@@ -1323,7 +1407,7 @@
         // This one should get deferred on set.
         setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
-        final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
         assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
                 mTestTimer.getElapsed());
 
@@ -1449,61 +1533,24 @@
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
         when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
-
         final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
-        long firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        // This one should get deferred on set.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent(), false);
-        long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+
+        testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-        }
-        // This one should get deferred after the latest alarm expires.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent(), false);
-        for (int i = 0; i < quota; i++) {
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+        testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
+                trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID);
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        firstTrigger = mNowElapsedTest + 10;
-        for (int i = 0; i < quota; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
-        }
-        // This delivery time maintains the quota invariant. Should not be deferred.
-        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5;
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger,
-                getNewMockPendingIntent(), false);
-        for (int i = 0; i < quota; i++) {
-            mNowElapsedTest = mTestTimer.getElapsed();
-            mTestTimer.expire();
-        }
-        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
-                mTestTimer.getElapsed());
+        testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
     }
 
     @Test
@@ -1545,6 +1592,259 @@
     }
 
     @Test
+    public void canScheduleExactAlarms() throws RemoteException {
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertTrue(mBinder.canScheduleExactAlarms());
+
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertFalse(mBinder.canScheduleExactAlarms());
+
+        doReturn(PermissionChecker.PERMISSION_SOFT_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        assertFalse(mBinder.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        // alarm clock
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                getNewMockPendingIntent(), null, null, null,
+                mock(AlarmManager.AlarmClockInfo.class));
+
+        // exact, allow-while-idle
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
+
+        // inexact, allow-while-idle
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void alarmClockBinderCallWhenChangeDisabled() throws Exception {
+        doReturn(false).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
+        mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                alarmPi, null, null, null, alarmClock);
+
+        verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
+                isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull());
+    }
+
+    @Test
+    public void alarmClockBinderCall() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
+        mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+                alarmPi, null, null, null, alarmClock);
+
+        // Correct permission checks are invoked.
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
+                isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE),
+                bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        // Permission check is granted by default by the mock.
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+        // If permission is denied, only then allowlist will be checked.
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                        Manifest.permission.SCHEDULE_EXACT_ALARM));
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM));
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                eq(alarmPi), isNull(), isNull(),
+                eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
+                eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void inexactAllowWhileIdleBinderCallWithoutAllowlist() throws RemoteException {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+                        anyString(), any(UserHandle.class)));
+
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
+                FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+        verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+                Manifest.permission.SCHEDULE_EXACT_ALARM), never());
+        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
+                eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
+                isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+        final Bundle idleOptions = bundleCaptor.getValue();
+        final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
+    }
+
+    @Test
+    public void idleOptionsSentOnExpiration() throws Exception {
+        final long triggerTime = mNowElapsedTest + 5000;
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        final Bundle idleOptions = new Bundle();
+        idleOptions.putChar("TEST_CHAR_KEY", 'x');
+        idleOptions.putInt("TEST_INT_KEY", 53);
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi, 0, 0, TEST_CALLING_UID,
+                idleOptions);
+
+        mNowElapsedTest = mTestTimer.getElapsed();
+        mTestTimer.expire();
+
+        verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
+                any(), any(Handler.class), isNull(), eq(idleOptions));
+    }
+
+    @Test
     public void alarmStoreMigration() {
         setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);
         final int numAlarms = 10;
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 42fa3d4..12894ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -69,12 +69,12 @@
                 mock(PendingIntent.class));
         return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
                 mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID,
-                TEST_CALLING_PACKAGE);
+                TEST_CALLING_PACKAGE, null);
     }
 
     private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) {
         return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class),
-                null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+                null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
     }
 
     private void addAlarmsToStore(Alarm... alarms) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
index e80f065..b64528c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
@@ -17,10 +17,17 @@
 package com.android.server.alarm;
 
 import static android.app.AlarmManager.ELAPSED_REALTIME;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+import static android.app.AlarmManager.FLAG_STANDALONE;
+import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
+import static android.app.AlarmManager.RTC_WAKEUP;
 
 import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.NUM_POLICIES;
 import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+import static com.android.server.alarm.AlarmManagerService.isExemptFromAppStandby;
 import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
 import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
@@ -28,7 +35,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.platform.test.annotations.Presubmit;
 
@@ -43,15 +52,29 @@
 @RunWith(AndroidJUnit4.class)
 public class AlarmTest {
 
-    private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) {
+    private Alarm createDefaultAlarm(long requestedElapsed, long windowLength, int flags) {
         return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0,
-                mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID,
-                TEST_CALLING_PACKAGE);
+                createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE, null);
+    }
+
+    private Alarm createAlarmClock(long requestedRtc) {
+        final AlarmManager.AlarmClockInfo info = mock(AlarmManager.AlarmClockInfo.class);
+        return new Alarm(RTC_WAKEUP, requestedRtc, requestedRtc, 0, 0, createAlarmSender(),
+                null, null, null, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, info, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE, null);
+    }
+
+    private PendingIntent createAlarmSender() {
+        final PendingIntent alarmPi = mock(PendingIntent.class);
+        when(alarmPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE);
+        when(alarmPi.getCreatorUid()).thenReturn(TEST_CALLING_UID);
+        return alarmPi;
     }
 
     @Test
     public void initSetsOnlyRequesterPolicy() {
-        final Alarm a = createDefaultAlarm(4567, 2);
+        final Alarm a = createDefaultAlarm(4567, 2, 0);
 
         for (int i = 0; i < NUM_POLICIES; i++) {
             if (i == REQUESTER_POLICY_INDEX) {
@@ -86,7 +109,7 @@
 
     @Test
     public void whenElapsed() {
-        final Alarm a = createDefaultAlarm(0, 0);
+        final Alarm a = createDefaultAlarm(0, 0, 0);
 
         final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES);
         for (int i = 0; i < NUM_POLICIES; i++) {
@@ -104,7 +127,7 @@
 
     @Test
     public void maxWhenElapsed() {
-        final Alarm a = createDefaultAlarm(10, 12);
+        final Alarm a = createDefaultAlarm(10, 12, 0);
         assertEquals(22, a.getMaxWhenElapsed());
 
         a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
@@ -128,7 +151,7 @@
 
     @Test
     public void setPolicyElapsedExact() {
-        final Alarm exactAlarm = createDefaultAlarm(10, 0);
+        final Alarm exactAlarm = createDefaultAlarm(10, 0, 0);
 
         assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
         assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -143,7 +166,7 @@
 
     @Test
     public void setPolicyElapsedInexact() {
-        final Alarm inexactAlarm = createDefaultAlarm(10, 5);
+        final Alarm inexactAlarm = createDefaultAlarm(10, 5, 0);
 
         assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
         assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -154,4 +177,20 @@
 
         assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
     }
+
+    @Test
+    public void isExemptFromStandby() {
+        final long anything = 35412;    // Arbitrary number, doesn't matter for this test.
+
+        assertFalse("Basic alarm exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, 0)));
+        assertFalse("FLAG_ALLOW_WHILE_IDLE_COMPAT exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_COMPAT)));
+
+        assertTrue("ALLOW_WHILE_IDLE not exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE)));
+        assertTrue("ALLOW_WHILE_IDLE_UNRESTRICTED not exempt", isExemptFromAppStandby(
+                createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)));
+        assertTrue("Alarm clock not exempt", isExemptFromAppStandby(createAlarmClock(anything)));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
index 5bb6a42..0e795a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
@@ -45,7 +45,7 @@
         }
         uidAlarms.add(new Alarm(
                 removeIt ? RTC : RTC_WAKEUP,
-                0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+                0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null));
         return all;
     }
 
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/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 2822d5c..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
@@ -28,6 +28,8 @@
     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;
@@ -45,6 +47,8 @@
         mAppForegroundHelper = new FakeAppForegroundHelper();
         mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
+        mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
+        mDeviceIdleHelper = new FakeDeviceIdleHelper();
         mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
@@ -91,6 +95,16 @@
     }
 
     @Override
+    public FakeDeviceStationaryHelper getDeviceStationaryHelper() {
+        return mDeviceStationaryHelper;
+    }
+
+    @Override
+    public FakeDeviceIdleHelper getDeviceIdleHelper() {
+        return mDeviceIdleHelper;
+    }
+
+    @Override
     public LocationAttributionHelper getLocationAttributionHelper() {
         return mLocationAttributionHelper;
     }
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/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/values.xml
similarity index 82%
rename from services/tests/servicestests/res/values/strings.xml
rename to services/tests/servicestests/res/values/values.xml
index 1f07ad5..0404ebc 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/values.xml
@@ -36,4 +36,12 @@
     <string name="module_2_name" translatable="false">module_2_name</string>
 
     <string name="widget_description">widget description string</string>
+    <dimen name="widget_min_width">80dp</dimen>
+    <dimen name="widget_min_height">40dp</dimen>
+    <dimen name="widget_min_resize_width">40dp</dimen>
+    <dimen name="widget_min_resize_height">20dp</dimen>
+    <dimen name="widget_max_resize_width">160dp</dimen>
+    <dimen name="widget_max_resize_height">80dp</dimen>
+    <integer name="widget_target_cell_width">2</integer>
+    <integer name="widget_target_cell_height">1</integer>
 </resources>
diff --git a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
index 72f025d..de06191 100644
--- a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
+++ b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
@@ -16,12 +16,18 @@
   -->
 
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
-  android:minWidth="40dp"
-  android:minHeight="40dp"
+  android:minWidth="@dimen/widget_min_width"
+  android:minHeight="@dimen/widget_min_height"
+  android:minResizeWidth="@dimen/widget_min_resize_width"
+  android:minResizeHeight="@dimen/widget_min_resize_height"
+  android:maxResizeWidth="@dimen/widget_max_resize_width"
+  android:maxResizeHeight="@dimen/widget_max_resize_height"
+  android:targetCellWidth="@integer/widget_target_cell_width"
+  android:targetCellHeight="@integer/widget_target_cell_height"
   android:updatePeriodMillis="86400000"
   android:previewImage="@drawable/icon1"
   android:previewLayout="@layout/widget_preview"
   android:resizeMode="horizontal|vertical"
   android:description="@string/widget_description"
   android:widgetCategory="home_screen">
-</appwidget-provider>
\ No newline at end of file
+</appwidget-provider>
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/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index a946534..8d54ead 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,6 +16,12 @@
 
 package com.android.server.am;
 
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -27,6 +33,7 @@
 import android.hardware.power.stats.PowerEntity;
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
+import android.util.IntArray;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -34,7 +41,9 @@
 import com.android.internal.os.BatteryStatsImpl;
 
 import org.junit.Before;
+import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -58,6 +67,69 @@
                 mBatteryStatsImpl);
     }
 
+    @Test
+    public void testTargetedEnergyConsumerQuerying() {
+        final int numCpuClusters = 4;
+        final int numOther = 3;
+
+        final IntArray tempAllIds = new IntArray();
+        // Add some energy consumers used by BatteryExternalStatsWorker.
+        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
+                "display");
+        tempAllIds.add(displayId);
+        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+
+        final int[] cpuClusterIds = new int[numCpuClusters];
+        for (int i = 0; i < numCpuClusters; i++) {
+            cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer(
+                    EnergyConsumerType.CPU_CLUSTER, i, "cpu_cluster" + i);
+            tempAllIds.add(cpuClusterIds[i]);
+            mPowerStatsInternal.incrementEnergyConsumption(cpuClusterIds[i], 1111 + i);
+        }
+        Arrays.sort(cpuClusterIds);
+
+        final int[] otherIds = new int[numOther];
+        for (int i = 0; i < numOther; i++) {
+            otherIds[i] = mPowerStatsInternal.addEnergyConsumer(
+                    EnergyConsumerType.OTHER, i, "other" + i);
+            tempAllIds.add(otherIds[i]);
+            mPowerStatsInternal.incrementEnergyConsumption(otherIds[i], 3000 + i);
+        }
+        Arrays.sort(otherIds);
+
+        final int[] allIds = tempAllIds.toArray();
+        Arrays.sort(allIds);
+
+        // Inform BESW that PowerStatsInternal is ready to query
+        mBatteryExternalStatsWorker.systemServicesReady();
+
+        final EnergyConsumerResult[] displayResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
+        // Results should only have the display energy consumer
+        assertEquals(1, displayResults.length);
+        assertEquals(displayId, displayResults[0].id);
+
+        final EnergyConsumerResult[] cpuResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
+        // Results should only have the cpu cluster energy consumers
+        final int[] receivedCpuIds = new int[cpuResults.length];
+        for (int i = 0; i < cpuResults.length; i++) {
+            receivedCpuIds[i] = cpuResults[i].id;
+        }
+        Arrays.sort(receivedCpuIds);
+        assertArrayEquals(cpuClusterIds, receivedCpuIds);
+
+        final EnergyConsumerResult[] allResults =
+                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_ALL).getNow(null);
+        // All energy consumer results should be available
+        final int[] receivedAllIds = new int[allResults.length];
+        for (int i = 0; i < allResults.length; i++) {
+            receivedAllIds[i] = allResults[i].id;
+        }
+        Arrays.sort(receivedAllIds);
+        assertArrayEquals(allIds, receivedAllIds);
+    }
+
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
@@ -79,9 +151,8 @@
     }
 
     public class TestPowerStatsInternal extends PowerStatsInternal {
-        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
-        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
-                new SparseArray<>();
+        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray();
+        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray();
         private final int mTimeSinceBoot = 0;
 
         @Override
@@ -98,10 +169,19 @@
         public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
                 int[] energyConsumerIds) {
             final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
-            final int size = mEnergyConsumerResults.size();
-            final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
-            for (int i = 0; i < size; i++) {
-                results[i] = mEnergyConsumerResults.valueAt(i);
+            final EnergyConsumerResult[] results;
+            final int length = energyConsumerIds.length;
+            if (length == 0) {
+                final int size = mEnergyConsumerResults.size();
+                results = new EnergyConsumerResult[size];
+                for (int i = 0; i < size; i++) {
+                    results[i] = mEnergyConsumerResults.valueAt(i);
+                }
+            } else {
+                results = new EnergyConsumerResult[length];
+                for (int i = 0; i < length; i++) {
+                    results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]);
+                }
             }
             future.complete(results);
             return future;
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 738f008..6d4189e 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -90,7 +90,7 @@
         writeGameServiceXml();
     }
 
-    private void verifyGameServiceSettingsData(Settings settings) {
+    private void verifyGameServiceSettingsData(GameManagerSettings settings) {
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
         assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
@@ -107,7 +107,7 @@
         /* write out files and read */
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
-        Settings settings = new Settings(context.getFilesDir());
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertThat(settings.readPersistentDataLocked(), is(true));
         verifyGameServiceSettingsData(settings);
     }
@@ -118,7 +118,7 @@
         // write out files and read
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
-        Settings settings = new Settings(context.getFilesDir());
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertThat(settings.readPersistentDataLocked(), is(true));
 
         // write out, read back in and verify the same
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/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 78eb2df..ff8fedc 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -103,6 +103,26 @@
         assertEquals(info.loadDescription(mTestContext), "widget description string");
     }
 
+    public void testParseSizeConfiguration() {
+        AppWidgetProviderInfo info =
+                mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
+
+        assertThat(info.minWidth).isEqualTo(getDimensionResource(R.dimen.widget_min_width));
+        assertThat(info.minHeight).isEqualTo(getDimensionResource(R.dimen.widget_min_height));
+        assertThat(info.minResizeWidth)
+                .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_width));
+        assertThat(info.minResizeHeight)
+                .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_height));
+        assertThat(info.maxResizeWidth)
+                .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_width));
+        assertThat(info.maxResizeHeight)
+                .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_height));
+        assertThat(info.targetCellWidth)
+                .isEqualTo(getIntegerResource(R.integer.widget_target_cell_width));
+        assertThat(info.targetCellHeight)
+                .isEqualTo(getIntegerResource(R.integer.widget_target_cell_height));
+    }
+
     public void testRequestPinAppWidget_otherProvider() {
         ComponentName otherProvider = null;
         for (AppWidgetProviderInfo provider : mManager.getInstalledProviders()) {
@@ -325,6 +345,14 @@
         latch.await();
     }
 
+    private int getDimensionResource(int resId) {
+        return mTestContext.getResources().getDimensionPixelSize(resId);
+    }
+
+    private int getIntegerResource(int resId) {
+        return mTestContext.getResources().getInteger(resId);
+    }
+
     private class TestContext extends ContextWrapper {
 
         public TestContext() {
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..6add8d1 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;
@@ -3992,6 +3993,30 @@
     }
 
     @Test
+    public void testGetSetNetworkSlicing() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setNetworkSlicingEnabled(false));
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isNetworkSlicingEnabled());
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE)));
+
+        mContext.callerPermissions.add(permission.READ_NETWORK_DEVICE_CONFIG);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE));
+        } catch (SecurityException se) {
+            fail("Threw SecurityException with right permission");
+        }
+
+        setupProfileOwner();
+        dpm.setNetworkSlicingEnabled(false);
+        assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
+    }
+
+    @Test
     public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4329,6 +4354,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 +7027,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/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/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/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 4bbf96f..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
@@ -684,8 +684,8 @@
                     newAddFontFamilyRequest("<family lang='en'>"
                             + "  <font>test.ttf</font>"
                             + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+            fail("Expect NullPointerException");
+        } catch (NullPointerException e) {
             // Expect
         }
     }
@@ -703,9 +703,10 @@
             dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
                     + "  <font>test.ttf</font>"
                     + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index bb57a69..1958cb0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -15,12 +15,9 @@
  */
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.tv.cec.V1_0.CecMessage;
-import android.hardware.tv.cec.V1_0.HotplugEvent;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.os.RemoteException;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -61,6 +58,7 @@
     private int mMyPhysicalAddress = 0;
     private HdmiPortInfo[] mHdmiPortInfo = null;
     private HdmiCecController.HdmiCecCallback mCallback = null;
+    private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
 
     @Override
     public String nativeInit() {
@@ -99,7 +97,7 @@
 
     @Override
     public int nativeGetVersion() {
-        return 0;
+        return mCecVersion;
     }
 
     @Override
@@ -135,24 +133,23 @@
         mPortConnectionStatus.put(port, connected);
     }
 
+    public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) {
+        mCecVersion = cecVersion;
+    }
+
     public void onCecMessage(HdmiCecMessage hdmiCecMessage) {
         if (mCallback == null) {
             return;
         }
-        CecMessage message = new CecMessage();
-        message.initiator = hdmiCecMessage.getSource();
-        message.destination = hdmiCecMessage.getDestination();
-        ArrayList<Byte> body = new ArrayList<>();
-        body.add((byte) hdmiCecMessage.getOpcode());
+        int source = hdmiCecMessage.getSource();
+        int destination = hdmiCecMessage.getDestination();
+        byte[] body = new byte[hdmiCecMessage.getParams().length + 1];
+        int i = 0;
+        body[i++] = (byte) hdmiCecMessage.getOpcode();
         for (byte param : hdmiCecMessage.getParams()) {
-            body.add(param);
+            body[i++] = param;
         }
-        message.body = body;
-        try {
-            mCallback.onCecMessage(message);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending CEC message", e);
-        }
+        mCallback.onCecMessage(source, destination, body);
     }
 
     public void onHotplugEvent(int port, boolean connected) {
@@ -160,15 +157,7 @@
             return;
         }
 
-        HotplugEvent hotplugEvent = new HotplugEvent();
-        hotplugEvent.portId = port;
-        hotplugEvent.connected = connected;
-
-        try {
-            mCallback.onHotplugEvent(hotplugEvent);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending hotplug event", e);
-        }
+        mCallback.onHotplugEvent(port, connected);
     }
 
     public List<HdmiCecMessage> getResultMessages() {
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 9f0d982..b11ac24 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;
@@ -23,6 +24,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -33,6 +36,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.sysprop.HdmiProperties;
@@ -49,6 +53,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
@@ -1550,6 +1555,31 @@
     }
 
     @Test
+    public void toggleAndFollowTvPower_isInteractive() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+        mActiveMediaSessionsPaused = false;
+        mWokenUp = false;
+
+        mHdmiControlService.toggleAndFollowTvPower();
+
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+        assertThat(mWokenUp).isFalse();
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_isNotInteractive() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        mActiveMediaSessionsPaused = false;
+        mWokenUp = false;
+
+        mHdmiControlService.toggleAndFollowTvPower();
+
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+        assertThat(mWokenUp).isTrue();
+    }
+
+
+    @Test
     public void shouldHandleTvPowerKey_CecDisabled() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
@@ -1586,4 +1616,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/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index be584d7..462f3e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -49,6 +49,7 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -622,6 +623,45 @@
         assertEquals(runnerUid, Binder.getCallingWorkSourceUid());
     }
 
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_limitToMinimumSupportedVersion() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+    }
+
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_limitToAtLeast1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(0x0);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+    }
+
+    @Ignore("b/180499471")
+    @Test
+    public void initCecVersion_useHighestMatchingVersion() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
+
+        mHdmiControlService.initService();
+        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+    }
+
     private static class VolumeControlFeatureCallback extends
             IHdmiCecVolumeControlFeatureListener.Stub {
         boolean mCallbackReceived = false;
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 f9b25d9..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;
 
@@ -496,6 +497,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRandom13() {
         assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES);
 
@@ -508,9 +510,9 @@
                 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[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9);
-        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, numTypesCdf, probStop);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 1f66c7c..67d6929 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -339,11 +339,11 @@
         mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
 
         // Verify fingerprint is removed
-        verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
-        verify(mFaceManager).remove(any(), eq(PRIMARY_USER_ID), any());
+        verify(mFingerprintManager).removeAll(eq(PRIMARY_USER_ID), any());
+        verify(mFaceManager).removeAll(eq(PRIMARY_USER_ID), any());
 
-        verify(mFingerprintManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
-        verify(mFaceManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any());
+        verify(mFingerprintManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any());
+        verify(mFaceManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any());
     }
 
     @Test
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/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index a112b14..ecff409 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -16,58 +16,104 @@
 
 package com.android.server.people;
 
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+
+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.eq;
+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.when;
 
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
+import android.app.people.IConversationListener;
+import android.app.people.IPeopleManager;
+import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTarget;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 
-@RunWith(JUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public final class PeopleServiceTest {
-
     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
     private static final int APP_PREDICTION_TARGET_COUNT = 4;
     private static final String TEST_PACKAGE_NAME = "com.example";
     private static final int USER_ID = 0;
+    private static final String CONVERSATION_ID_1 = "12";
+    private static final String CONVERSATION_ID_2 = "123";
 
     private PeopleServiceInternal mServiceInternal;
     private PeopleService.LocalService mLocalService;
     private AppPredictionSessionId mSessionId;
     private AppPredictionContext mPredictionContext;
 
-    @Mock private Context mContext;
-    @Mock private IPredictionCallback mCallback;
+    @Mock
+    private Context mMockContext;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    protected TestableContext getContext() {
+        return mContext;
+    }
+
+    @Mock
+    private IPredictionCallback mCallback;
+    private TestableLooper mTestableLooper;
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private TestablePeopleService mPeopleService;
+    private IPeopleManager mIPeopleManager;
+    private PeopleManager mPeopleManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        mPeopleService = new TestablePeopleService(mContext);
+        mTestableLooper = TestableLooper.get(this);
+        mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
+        mPeopleManager = new PeopleManager(mContext, mIPeopleManager);
+        when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
         when(mCallback.asBinder()).thenReturn(new Binder());
-
         PeopleService service = new PeopleService(mContext);
         service.onStart(/* isForTesting= */ true);
 
@@ -75,7 +121,7 @@
         mLocalService = (PeopleService.LocalService) mServiceInternal;
 
         mSessionId = new AppPredictionSessionId("abc", USER_ID);
-        mPredictionContext = new AppPredictionContext.Builder(mContext)
+        mPredictionContext = new AppPredictionContext.Builder(mMockContext)
                 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
                 .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
                 .setExtras(new Bundle())
@@ -111,4 +157,134 @@
 
         mServiceInternal.onDestroyPredictionSession(mSessionId);
     }
+
+    @Test
+    public void testRegisterConversationListener() throws Exception {
+        assertEquals(0,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(1,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(2,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
+                new TestableConversationListener());
+        mTestableLooper.processAllMessages();
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+    }
+
+    @Test
+    public void testUnregisterConversationListener() throws Exception {
+        TestableConversationListener listener1 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                listener1);
+        TestableConversationListener listener2 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
+                listener2);
+        TestableConversationListener listener3 = new TestableConversationListener();
+        mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
+                listener3);
+        mTestableLooper.processAllMessages();
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        mIPeopleManager.unregisterConversationListener(
+                listener2);
+        assertEquals(2,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+        mIPeopleManager.unregisterConversationListener(
+                listener1);
+        assertEquals(1,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+        mIPeopleManager.unregisterConversationListener(
+                listener3);
+        assertEquals(0,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+    }
+
+    @Test
+    public void testOnlyTriggersConversationListenersForRegisteredConversation() {
+        PeopleManager.ConversationListener listenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, listenerForConversation1);
+        PeopleManager.ConversationListener secondListenerForConversation1 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_1, secondListenerForConversation1);
+        PeopleManager.ConversationListener listenerForConversation2 = mock(
+                PeopleManager.ConversationListener.class);
+        registerListener(CONVERSATION_ID_2, listenerForConversation2);
+        assertEquals(3,
+                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+
+        // Update conversation with two listeners.
+        ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
+                ACTIVITY_GAME).build();
+        mPeopleService.mConversationListenerHelper.onConversationsUpdate(
+                Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
+        mTestLooper.dispatchAll();
+
+        // Never update listeners for other conversations.
+        verify(listenerForConversation2, never()).onConversationUpdate(any());
+        // Should update both listeners for the conversation.
+        ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
+                ConversationChannel.class);
+        verify(listenerForConversation1, times(1)).onConversationUpdate(
+                capturedConversation.capture());
+        ConversationChannel conversationChannel = capturedConversation.getValue();
+        verify(secondListenerForConversation1, times(1)).onConversationUpdate(
+                eq(conversationChannel));
+        assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
+        assertThat(conversationChannel.getStatuses()).containsExactly(status);
+    }
+
+    private void registerListener(String conversationId,
+            PeopleManager.ConversationListener listener) {
+        mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
+                conversationId, listener,
+                mTestLooper.getNewExecutor());
+    }
+
+    private ConversationChannel getConversation(String shortcutId, ConversationStatus status) {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
+                shortcutId).setLongLabel(
+                "name").build();
+        NotificationChannel notificationChannel = new NotificationChannel("123",
+                "channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        return new ConversationChannel(shortcutInfo, 0,
+                notificationChannel, null,
+                123L, false, false, Arrays.asList(status));
+    }
+
+    private class TestableConversationListener extends IConversationListener.Stub {
+        @Override
+        public void onConversationUpdate(ConversationChannel conversation) {
+        }
+    }
+
+    // Use a Testable subclass so we can simulate calls from the system without failing.
+    private static class TestablePeopleService extends PeopleService {
+        TestablePeopleService(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart(true);
+        }
+
+        @Override
+        protected void enforceSystemRootOrSystemUI(Context context, String message) {
+            return;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 6e57896..7709edb 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -79,6 +79,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.ContactsContract;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -91,8 +92,11 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.people.PeopleService;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
+import com.google.common.collect.Iterables;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -123,6 +127,7 @@
     private static final String TEST_PKG_NAME = "pkg";
     private static final String TEST_CLASS_NAME = "class";
     private static final String TEST_SHORTCUT_ID = "sc";
+    private static final String TEST_SHORTCUT_ID_2 = "sc2";
     private static final int TEST_PKG_UID = 35;
     private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
     private static final String PHONE_NUMBER = "+1234567890";
@@ -157,6 +162,7 @@
     private ShortcutChangeCallback mShortcutChangeCallback;
     private ShortcutInfo mShortcutInfo;
     private TestInjector mInjector;
+    private TestLooper mLooper;
 
     @Before
     public void setUp() throws PackageManager.NameNotFoundException {
@@ -237,7 +243,8 @@
         mCancellationSignal = new CancellationSignal();
 
         mInjector = new TestInjector();
-        mDataManager = new DataManager(mContext, mInjector);
+        mLooper = new TestLooper();
+        mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
         mDataManager.initialize();
 
         when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
@@ -515,6 +522,84 @@
     }
 
     @Test
+    public void testAddConversationsListener() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel);
+        verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
+        mDataManager.notifyConversationsListeners(changedConversations);
+        mLooper.dispatchAll();
+
+        verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
+    }
+
+    @Test
+    public void testAddConversationListenersNotifiesMultipleConversations() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID_2,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut2);
+        ConversationChannel conversationChannel2 = mDataManager.getConversation(TEST_PKG_NAME,
+                USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID_2);
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel,
+                conversationChannel2);
+        verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
+        mDataManager.notifyConversationsListeners(changedConversations);
+        mLooper.dispatchAll();
+
+        verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
+        ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
+                List.class);
+        verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
+        assertThat(capturedConversation.getValue()).containsExactly(conversationChannel,
+                conversationChannel2);
+    }
+
+    @Test
+    public void testAddOrUpdateStatusNotifiesConversationsListeners() throws Exception {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        PeopleService.ConversationsListener listener = mock(
+                PeopleService.ConversationsListener.class);
+        mDataManager.addConversationsListener(listener);
+
+        ConversationStatus status = new ConversationStatus.Builder("cs1", ACTIVITY_GAME).build();
+        mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, status);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
+                List.class);
+        verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
+        ConversationChannel result = Iterables.getOnlyElement(capturedConversation.getValue());
+        assertThat(result.getStatuses()).containsExactly(status);
+        assertEquals(result.getShortcutInfo().getId(), TEST_SHORTCUT_ID);
+    }
+
+    @Test
     public void testGetConversationReturnsCustomizedConversation() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
@@ -601,6 +686,22 @@
     }
 
     @Test
+    public void testIsConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isFalse();
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isTrue();
+        assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID + "1")).isFalse();
+    }
+
+    @Test
     public void testNotificationChannelCreated() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         mDataManager.onUserUnlocked(USER_ID_SECONDARY);
@@ -959,7 +1060,7 @@
         mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
         byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
 
-        DataManager dataManager = new DataManager(mContext, mInjector);
+        DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
         dataManager.onUserUnlocked(USER_ID_PRIMARY);
         dataManager.restore(USER_ID_PRIMARY, payload);
         ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 23fcf70..4d0beef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -103,7 +103,6 @@
 import android.util.Xml;
 
 import com.android.frameworks.servicestests.R;
-import com.android.internal.util.FastXmlSerializer;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
@@ -111,7 +110,6 @@
 import org.mockito.ArgumentCaptor;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -141,6 +139,7 @@
 
     private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
     private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+    private static final int CACHE_OWNER_2 = LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
     @Override
     protected void tearDown() throws Exception {
@@ -1531,7 +1530,8 @@
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
                     makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
-                    makeLongLivedShortcut("s4"))));
+                    makeLongLivedShortcut("s4"), makeLongLivedShortcut("s5"),
+                    makeLongLivedShortcut("s6"))));
         });
 
         // Pin s2
@@ -1545,28 +1545,30 @@
             mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
                     HANDLE_USER_0, CACHE_OWNER_0);
-            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4", "s5"),
                     HANDLE_USER_0, CACHE_OWNER_1);
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+                    HANDLE_USER_0, CACHE_OWNER_2);
         });
 
         setCaller(CALLING_PACKAGE_1);
 
         // Get dynamic shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2", "s3", "s4");
+                "s1", "s2", "s3", "s4", "s5", "s6");
         // Get pinned shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
                 "s2");
         // Get cached shortcuts
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         // Remove a dynamic cached shortcut
-        mManager.removeDynamicShortcuts(list("s4"));
+        mManager.removeDynamicShortcuts(list("s4", "s5"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s2", "s3");
+                "s1", "s2", "s3", "s6");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1574,15 +1576,21 @@
         });
         // s2 still cached by owner1. s4 wasn't cached by owner0 so didn't get removed.
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s4");
+                "s2", "s4", "s5", "s6");
 
         // uncache a non-dynamic shortcut. Should be removed.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
                     HANDLE_USER_0, CACHE_OWNER_1);
         });
+
+        // uncache s6 by its only owner. s5 still cached by owner1
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+                    HANDLE_USER_0, CACHE_OWNER_2);
+        });
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2");
+                "s2", "s5");
 
         // Cache another shortcut
         runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -1590,14 +1598,14 @@
                     HANDLE_USER_0, CACHE_OWNER_0);
         });
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s2", "s3");
+                "s2", "s3", "s5");
 
         // Remove a dynamic cached pinned long lived shortcut
         mManager.removeLongLivedShortcuts(list("s2"));
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
-                "s1", "s3");
+                "s1", "s3", "s6");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
-                "s3");
+                "s3", "s5");
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
                 "s2");
     }
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/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 03e60af..ddbe81c 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.powerstats;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,6 +35,9 @@
 
 import com.android.server.SystemService;
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
+import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
+import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
 import com.android.server.powerstats.nano.PowerEntityProto;
 import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
 import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
@@ -52,6 +56,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -63,15 +68,18 @@
 public class PowerStatsServiceTest {
     private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
     private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
-    private static final String METER_FILENAME = "metertest";
-    private static final String MODEL_FILENAME = "modeltest";
-    private static final String RESIDENCY_FILENAME = "residencytest";
+    private static final String METER_FILENAME = "log.powerstats.metertest.0";
+    private static final String MODEL_FILENAME = "log.powerstats.modeltest.0";
+    private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0";
     private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
     private static final String CHANNEL_NAME = "channelname";
     private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
     private static final String POWER_ENTITY_NAME = "powerentityinfo";
     private static final String STATE_NAME = "stateinfo";
     private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
+    private static final String METER_CACHE_FILENAME = "meterCacheTest";
+    private static final String MODEL_CACHE_FILENAME = "modelCacheTest";
+    private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest";
     private static final int ENERGY_METER_COUNT = 8;
     private static final int ENERGY_CONSUMER_COUNT = 2;
     private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
@@ -90,12 +98,12 @@
         private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
         @Override
         File createDataStoragePath() {
-            mDataStorageDir = null;
-
-            try {
-                mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
-            } catch (IOException e) {
-                fail("Could not create temp directory.");
+            if (mDataStorageDir == null) {
+                try {
+                    mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
+                } catch (IOException e) {
+                    fail("Could not create temp directory.");
+                }
             }
 
             return mDataStorageDir;
@@ -117,16 +125,36 @@
         }
 
         @Override
+        String createMeterCacheFilename() {
+            return METER_CACHE_FILENAME;
+        }
+
+        @Override
+        String createModelCacheFilename() {
+            return MODEL_CACHE_FILENAME;
+        }
+
+        @Override
+        String createResidencyCacheFilename() {
+            return RESIDENCY_CACHE_FILENAME;
+        }
+
+        @Override
         IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
             return mTestPowerStatsHALWrapper;
         }
 
         @Override
         PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String modelFilename, String residencyFilename,
+                String meterFilename, String meterCacheFilename,
+                String modelFilename, String modelCacheFilename,
+                String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
-                modelFilename, residencyFilename, powerStatsHALWrapper);
+            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+                meterFilename, meterCacheFilename,
+                modelFilename, modelCacheFilename,
+                residencyFilename, residencyCacheFilename,
+                powerStatsHALWrapper);
             return mPowerStatsLogger;
         }
 
@@ -665,4 +693,315 @@
         // input buffer had only length and no data.
         assertTrue(pssProto.stateResidencyResult.length == 0);
     }
+
+    @Test
+    public void testDataStorageDeletedMeterMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Generate random array of bytes to emulate cached meter data.  Store to file.
+        Random rd = new Random();
+        byte[] bytes = new byte[100];
+        rd.nextBytes(bytes);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached meter data is just random bytes it won't match the data read from the HAL.
+        // This mismatch of cached and current HAL data should force a delete.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached meter data was updated to new HAL output.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytesExpected = ChannelUtils.getProtoBytes(channels);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedModelMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached energy consumer data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached energy consumer data is just random bytes it won't match the data read from
+        // the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Verify cached energy consumer data was updated to new HAL output.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageDeletedResidencyMismatch() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Generate random array of bytes to emulate cached power entity info data.  Store to file.
+        Random rd = new Random();
+        bytes = new byte[100];
+        rd.nextBytes(bytes);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since cached power entity info data is just random bytes it won't match the data read
+        // from the HAL.  This mismatch of cached and current HAL data should force a delete.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertFalse(residencyFile.exists());
+
+        // Verify cached power entity data was updated to new HAL output.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+        FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+        onDeviceStorageFis.read(bytesActual);
+        assertTrue(Arrays.equals(bytesExpected, bytesActual));
+    }
+
+    @Test
+    public void testDataStorageNotDeletedNoCachedData() throws IOException {
+        // Create the directory where log files will be stored.
+        mInjector.createDataStoragePath();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // This test mimics the device's first boot where there is no cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // Since there is no cached data on the first boot any log files that happen to exist
+        // should be deleted.
+        assertTrue(mService.getDeleteMeterDataOnBoot());
+        assertTrue(mService.getDeleteModelDataOnBoot());
+        assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were deleted.
+        assertFalse(meterFile.exists());
+        assertFalse(modelFile.exists());
+        assertFalse(residencyFile.exists());
+    }
+
+    @Test
+    public void testDataStorageNotDeletedAllDataMatches() throws IOException {
+        // Create the directory where cached data will be stored.
+        mInjector.createDataStoragePath();
+
+        // In order to create cached data that will match the current data read by the
+        // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+        // returned from the Injector.
+        IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+        // Create cached channel data and write to file.
+        Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+        byte[] bytes = ChannelUtils.getProtoBytes(channels);
+        File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+        FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached energy consumer data and write to file.
+        EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+        bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create cached power entity info data and write to file.
+        PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+        bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+        onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+        onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+        onDeviceStorageFos.write(bytes);
+        onDeviceStorageFos.close();
+
+        // Create log files.
+        File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+        File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+        File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+        meterFile.createNewFile();
+        modelFile.createNewFile();
+        residencyFile.createNewFile();
+
+        // Verify log files exist.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+
+        // Boot device after creating old cached data.
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        // All cached data created above should match current data read in PowerStatsService so we
+        // expect the data not to be deleted.
+        assertFalse(mService.getDeleteMeterDataOnBoot());
+        assertFalse(mService.getDeleteModelDataOnBoot());
+        assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+        // Verify log files were not deleted.
+        assertTrue(meterFile.exists());
+        assertTrue(modelFile.exists());
+        assertTrue(residencyFile.exists());
+    }
 }
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/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 da3d1d6..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;
@@ -533,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/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/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/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 15e045c..2fdd63e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -16,8 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
 
@@ -94,10 +97,19 @@
         mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
     }
 
+    private WindowState createAppOverlayWindow() {
+        final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY,
+                "testOverlayWindow");
+        win.mActivityRecord = null;
+        win.mHasSurface = true;
+        return win;
+    }
+
     @Test
     public void testRun() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        final WindowState overlayWin = createAppOverlayWindow();
         try {
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                     new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
@@ -109,12 +121,12 @@
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
-            final ArgumentCaptor<RemoteAnimationTarget[]> nonApsCaptor =
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
             verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
-                    appsCaptor.capture(), wallpapersCaptor.capture(), nonApsCaptor.capture(),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
@@ -130,6 +142,7 @@
             finishedCaptor.getValue().onAnimationFinished();
             verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                     eq(adapter));
+            assertEquals(0, nonAppsCaptor.getValue().length);
         } finally {
             mDisplayContent.mOpeningApps.clear();
         }
@@ -424,6 +437,89 @@
         }
     }
 
+    @Test
+    public void testNonAppIncluded_keygaurdGoingAway() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        // Add overlay window hidden by the keyguard.
+        final WindowState overlayWin = createAppOverlayWindow();
+        overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win.mActivityRecord, new Point(50, 100), null,
+                    new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
+            mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, appsCaptor.getValue().length);
+            final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+            assertEquals(new Point(50, 100), app.position);
+            assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+            assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex);
+            assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId);
+            assertEquals(mMockLeash, app.leash);
+            assertEquals(false, app.isTranslucent);
+            verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+            verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
+
+            finishedCaptor.getValue().onAnimationFinished();
+            verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                    eq(adapter));
+            assertEquals(1, nonAppsCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testNonAppIncluded_keygaurdGoingAwayToWallpaper() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+        // Add overlay window hidden by the keyguard.
+        final WindowState overlayWin = createAppOverlayWindow();
+        overlayWin.hide(false /* doAnimation */, false /* requestAnim */);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win.mActivityRecord, new Point(50, 100), null,
+                    new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
+            mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+            assertEquals(1, nonAppsCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);
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/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index e4b865f..86d8eee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -91,11 +91,6 @@
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
-    @Override
-    public boolean canBeHiddenByKeyguardLw(WindowState win) {
-        return false;
-    }
-
     /**
      * Sets a runnable to run when adding a splash screen which gets executed after the window has
      * been added but before returning the surface.
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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 83b30a9..c13d6b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1144,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/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/telecomm/java/android/telecom/CallScreeningService.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl
new file mode 100644
index 0000000..87b5138
--- /dev/null
+++ b/telecomm/java/android/telecom/CallScreeningService.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallScreeningService.ParcelableCallResponse;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 7988b03..deeb433 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -30,12 +31,18 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -132,7 +139,10 @@
                                 .createFromParcelableCall((ParcelableCall) args.arg2);
                         onScreenCall(callDetails);
                         if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) {
-                            mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
+                            mCallScreeningAdapter.onScreeningResponse(
+                                    callDetails.getTelecomCallId(),
+                                    new ComponentName(getPackageName(), getClass().getName()),
+                                    null);
                         }
                     } catch (RemoteException e) {
                         Log.w(this, "Exception when screening call: " + e);
@@ -157,10 +167,11 @@
 
     private ICallScreeningAdapter mCallScreeningAdapter;
 
-    /*
-     * Information about how to respond to an incoming call.
+    /**
+     * Parcelable version of {@link CallResponse} used to do IPC.
+     * @hide
      */
-    public static class CallResponse {
+    public static class ParcelableCallResponse implements Parcelable {
         private final boolean mShouldDisallowCall;
         private final boolean mShouldRejectCall;
         private final boolean mShouldSilenceCall;
@@ -168,13 +179,168 @@
         private final boolean mShouldSkipNotification;
         private final boolean mShouldScreenCallViaAudioProcessing;
 
+        private final int mCallComposerAttachmentsToShow;
+
+        private ParcelableCallResponse(
+                boolean shouldDisallowCall,
+                boolean shouldRejectCall,
+                boolean shouldSilenceCall,
+                boolean shouldSkipCallLog,
+                boolean shouldSkipNotification,
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
+            mShouldDisallowCall = shouldDisallowCall;
+            mShouldRejectCall = shouldRejectCall;
+            mShouldSilenceCall = shouldSilenceCall;
+            mShouldSkipCallLog = shouldSkipCallLog;
+            mShouldSkipNotification = shouldSkipNotification;
+            mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+        }
+
+        protected ParcelableCallResponse(Parcel in) {
+            mShouldDisallowCall = in.readBoolean();
+            mShouldRejectCall = in.readBoolean();
+            mShouldSilenceCall = in.readBoolean();
+            mShouldSkipCallLog = in.readBoolean();
+            mShouldSkipNotification = in.readBoolean();
+            mShouldScreenCallViaAudioProcessing = in.readBoolean();
+            mCallComposerAttachmentsToShow = in.readInt();
+        }
+
+        public CallResponse toCallResponse() {
+            return new CallResponse.Builder()
+                    .setDisallowCall(mShouldDisallowCall)
+                    .setRejectCall(mShouldRejectCall)
+                    .setSilenceCall(mShouldSilenceCall)
+                    .setSkipCallLog(mShouldSkipCallLog)
+                    .setSkipNotification(mShouldSkipNotification)
+                    .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing)
+                    .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow)
+                    .build();
+        }
+
+        public boolean shouldDisallowCall() {
+            return mShouldDisallowCall;
+        }
+
+        public boolean shouldRejectCall() {
+            return mShouldRejectCall;
+        }
+
+        public boolean shouldSilenceCall() {
+            return mShouldSilenceCall;
+        }
+
+        public boolean shouldSkipCallLog() {
+            return mShouldSkipCallLog;
+        }
+
+        public boolean shouldSkipNotification() {
+            return mShouldSkipNotification;
+        }
+
+        public boolean shouldScreenCallViaAudioProcessing() {
+            return mShouldScreenCallViaAudioProcessing;
+        }
+
+        public int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        public static final Creator<ParcelableCallResponse> CREATOR =
+                new Creator<ParcelableCallResponse>() {
+                    @Override
+                    public ParcelableCallResponse createFromParcel(Parcel in) {
+                        return new ParcelableCallResponse(in);
+                    }
+
+                    @Override
+                    public ParcelableCallResponse[] newArray(int size) {
+                        return new ParcelableCallResponse[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBoolean(mShouldDisallowCall);
+            dest.writeBoolean(mShouldRejectCall);
+            dest.writeBoolean(mShouldSilenceCall);
+            dest.writeBoolean(mShouldSkipCallLog);
+            dest.writeBoolean(mShouldSkipNotification);
+            dest.writeBoolean(mShouldScreenCallViaAudioProcessing);
+            dest.writeInt(mCallComposerAttachmentsToShow);
+        }
+    }
+
+    /**
+     * Information about how to respond to an incoming call. Call screening apps can construct an
+     * instance of this class using {@link CallResponse.Builder}.
+     */
+    public static class CallResponse {
+        /**
+         * Bit flag indicating whether to show the picture attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1;
+
+        /**
+         * Bit flag indicating whether to show the location attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1;
+
+        /**
+         * Bit flag indicating whether to show the subject attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2;
+
+        /**
+         * Bit flag indicating whether to show the priority attachment for call composer.
+         *
+         * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+         */
+        public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true,
+                value = {
+                        CALL_COMPOSER_ATTACHMENT_PICTURE,
+                        CALL_COMPOSER_ATTACHMENT_LOCATION,
+                        CALL_COMPOSER_ATTACHMENT_SUBJECT,
+                        CALL_COMPOSER_ATTACHMENT_PRIORITY
+                }
+        )
+        public @interface CallComposerAttachmentType {}
+
+        private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4;
+
+        private final boolean mShouldDisallowCall;
+        private final boolean mShouldRejectCall;
+        private final boolean mShouldSilenceCall;
+        private final boolean mShouldSkipCallLog;
+        private final boolean mShouldSkipNotification;
+        private final boolean mShouldScreenCallViaAudioProcessing;
+        private final int mCallComposerAttachmentsToShow;
+
         private CallResponse(
                 boolean shouldDisallowCall,
                 boolean shouldRejectCall,
                 boolean shouldSilenceCall,
                 boolean shouldSkipCallLog,
                 boolean shouldSkipNotification,
-                boolean shouldScreenCallViaAudioProcessing) {
+                boolean shouldScreenCallViaAudioProcessing,
+                int callComposerAttachmentsToShow) {
             if (!shouldDisallowCall
                     && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
                 throw new IllegalStateException("Invalid response state for allowed call.");
@@ -190,6 +356,7 @@
             mShouldSkipNotification = shouldSkipNotification;
             mShouldSilenceCall = shouldSilenceCall;
             mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+            mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
         }
 
         /*
@@ -237,6 +404,49 @@
             return mShouldScreenCallViaAudioProcessing;
         }
 
+        /**
+         * @return A bitmask of call composer attachments that should be shown to the user.
+         */
+        public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() {
+            return mCallComposerAttachmentsToShow;
+        }
+
+        /** @hide */
+        public ParcelableCallResponse toParcelable() {
+            return new ParcelableCallResponse(
+                    mShouldDisallowCall,
+                    mShouldRejectCall,
+                    mShouldSilenceCall,
+                    mShouldSkipCallLog,
+                    mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow
+            );
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            CallResponse that = (CallResponse) o;
+            return mShouldDisallowCall == that.mShouldDisallowCall &&
+                    mShouldRejectCall == that.mShouldRejectCall &&
+                    mShouldSilenceCall == that.mShouldSilenceCall &&
+                    mShouldSkipCallLog == that.mShouldSkipCallLog &&
+                    mShouldSkipNotification == that.mShouldSkipNotification &&
+                    mShouldScreenCallViaAudioProcessing
+                            == that.mShouldScreenCallViaAudioProcessing &&
+                    mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall,
+                    mShouldSkipCallLog, mShouldSkipNotification,
+                    mShouldScreenCallViaAudioProcessing,
+                    mCallComposerAttachmentsToShow);
+        }
+
         public static class Builder {
             private boolean mShouldDisallowCall;
             private boolean mShouldRejectCall;
@@ -244,6 +454,7 @@
             private boolean mShouldSkipCallLog;
             private boolean mShouldSkipNotification;
             private boolean mShouldScreenCallViaAudioProcessing;
+            private int mCallComposerAttachmentsToShow = -1;
 
             /**
              * Sets whether the incoming call should be blocked.
@@ -329,6 +540,38 @@
                 return this;
             }
 
+            /**
+             * Sets the call composer attachments that should be shown to the user.
+             *
+             * Attachments that are not shown will not be passed to the in-call UI responsible for
+             * displaying the call to the user.
+             *
+             * If this method is not called on a {@link Builder}, all attachments will be shown,
+             * except pictures, which will only be shown to users if the call is from a contact.
+             *
+             * Setting attachments to show will have no effect if the call screening service does
+             * not belong to the same package as the system dialer (as returned by
+             * {@link TelecomManager#getSystemDialerPackage()}).
+             *
+             * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show.
+             */
+            public @NonNull Builder setCallComposerAttachmentsToShow(
+                    @CallComposerAttachmentType int callComposerAttachmentsToShow) {
+                // If the argument is less than zero (meaning unset), no-op since the conversion
+                // to/from the parcelable version may call with that value.
+                if (callComposerAttachmentsToShow < 0) {
+                    return this;
+                }
+
+                if ((callComposerAttachmentsToShow
+                        & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) {
+                    throw new IllegalArgumentException("Attachment types must match the ones"
+                            + " defined in CallResponse");
+                }
+                mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+                return this;
+            }
+
             public CallResponse build() {
                 return new CallResponse(
                         mShouldDisallowCall,
@@ -336,7 +579,8 @@
                         mShouldSilenceCall,
                         mShouldSkipCallLog,
                         mShouldSkipNotification,
-                        mShouldScreenCallViaAudioProcessing);
+                        mShouldScreenCallViaAudioProcessing,
+                        mCallComposerAttachmentsToShow);
             }
        }
     }
@@ -423,21 +667,12 @@
     public final void respondToCall(@NonNull Call.Details callDetails,
             @NonNull CallResponse response) {
         try {
-            if (response.getDisallowCall()) {
-                mCallScreeningAdapter.disallowCall(
-                        callDetails.getTelecomCallId(),
-                        response.getRejectCall(),
-                        !response.getSkipCallLog(),
-                        !response.getSkipNotification(),
-                        new ComponentName(getPackageName(), getClass().getName()));
-            } else if (response.getSilenceCall()) {
-                mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
-            } else if (response.getShouldScreenCallViaAudioProcessing()) {
-                mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
-            } else {
-                mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
-            }
+            mCallScreeningAdapter.onScreeningResponse(
+                    callDetails.getTelecomCallId(),
+                    new ComponentName(getPackageName(), getClass().getName()),
+                    response.toParcelable());
         } catch (RemoteException e) {
+            Log.e(this, e, "Got remote exception when returning response");
         }
     }
 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 089a948..942a54e 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3390,11 +3390,20 @@
      *                  {@code true}, {@link #onDisconnect()} will be called soon after
      *                  this is called.
      * @param isInContacts Indicates whether the caller is in the user's contacts list.
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { }
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) { }
 
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 170ed3e..966ece3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -759,6 +759,8 @@
 
         @Override
         public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+                CallScreeningService.ParcelableCallResponse callScreeningResponse,
+                boolean isResponseFromSystemDialer,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED);
             try {
@@ -766,7 +768,9 @@
                 args.arg1 = callId;
                 args.arg2 = isBlocked;
                 args.arg3 = isInContacts;
-                args.arg4 = Log.createSubsession();
+                args.arg4 = callScreeningResponse;
+                args.arg5 = isResponseFromSystemDialer;
+                args.arg6 = Log.createSubsession();
                 mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget();
             } finally {
                 Log.endSession();
@@ -1437,12 +1441,16 @@
                 case MSG_ON_CALL_FILTERING_COMPLETED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        Log.continueSession((Session) args.arg4,
+                        Log.continueSession((Session) args.arg6,
                                 SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED);
                         String callId = (String) args.arg1;
                         boolean isBlocked = (boolean) args.arg2;
                         boolean isInContacts = (boolean) args.arg3;
-                        onCallFilteringCompleted(callId, isBlocked, isInContacts);
+                        CallScreeningService.ParcelableCallResponse callScreeningResponse =
+                                (CallScreeningService.ParcelableCallResponse) args.arg4;
+                        boolean isResponseFromSystemDialer = (boolean) args.arg5;
+                        onCallFilteringCompleted(callId, isBlocked, isInContacts,
+                                callScreeningResponse, isResponseFromSystemDialer);
                     } finally {
                         args.recycle();
                         Log.endSession();
@@ -2458,11 +2466,16 @@
         }
     }
 
-    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) {
-        Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts);
+    private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+            CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
+        Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId,
+                isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer);
         Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted");
         if (connection != null) {
-            connection.onCallFilteringCompleted(isBlocked, isInContacts);
+            connection.onCallFilteringCompleted(isBlocked, isInContacts,
+                    callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(),
+                    isResponseFromSystemDialer);
         }
     }
 
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index feb2ca5..6c6097a 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1204,15 +1204,25 @@
      * the results of a contacts lookup for the remote party.
      * @param isBlocked Whether call filtering indicates that the call should be blocked
      * @param isInContacts Whether the remote party is in the user's contacts
+     * @param callScreeningResponse The response that was returned from the
+     *                              {@link CallScreeningService} that handled this call. If no
+     *                              response was received from a call screening service,
+     *                              this will be {@code null}.
+     * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+     *                                  system dialer. If {@code callScreeningResponse} is
+     *                                  {@code null}, this will be {@code false}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
-    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            @Nullable CallScreeningService.CallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer) {
         Log.startSession("RC.oCFC", getActiveOwnerInfo());
         try {
             if (mConnected) {
                 mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts,
+                        callScreeningResponse.toParcelable(), isResponseFromSystemDialer,
                         null /*Session.Info*/);
             }
         } catch (RemoteException ignored) {
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index 83c8f62..0f2e178 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
+import android.telecom.CallScreeningService;
 
 /**
  * Internal remote callback interface for call screening services.
@@ -26,16 +27,6 @@
  * {@hide}
  */
 oneway interface ICallScreeningAdapter {
-    void allowCall(String callId);
-
-    void silenceCall(String callId);
-
-    void screenCallFurther(String callId);
-
-    void disallowCall(
-            String callId,
-            boolean shouldReject,
-            boolean shouldAddToCallLog,
-            boolean shouldShowNotification,
-            in ComponentName componentName);
+    void onScreeningResponse(String callId, in ComponentName componentName,
+            in CallScreeningService.ParcelableCallResponse response);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 301c2bb..7599e18 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
 import android.telecom.PhoneAccountHandle;
@@ -119,7 +120,8 @@
     void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
 
     void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
-            in Session.Info sessionInfo);
+            in CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer, in Session.Info sessionInfo);
 
     void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3f6162d..6d3fa81 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4601,8 +4601,9 @@
             "mmi_two_digit_number_pattern_string_array";
 
     /**
-     * Holds the list of carrier certificate hashes.
-     * Note that each carrier has its own certificates.
+     * Holds the list of carrier certificate hashes, followed by optional package names.
+     * Format: "sha1/256" or "sha1/256:package1,package2,package3..."
+     * Note that each carrier has its own hashes.
      */
     public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
             "carrier_certificate_string_array";
@@ -4763,6 +4764,14 @@
     public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL =
             "store_sim_pin_for_unattended_reboot_bool";
 
+     /**
+     * Determine whether "Enable 2G" toggle can be shown.
+     *
+     * Used to trade privacy/security against potentially reduced carrier coverage for some
+     * carriers.
+     */
+    public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5320,6 +5329,7 @@
         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);
+        sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ee3a0ef..6a9b71d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4461,6 +4461,10 @@
      * This functionality is only available to the app filling the {@link RoleManager#ROLE_DIALER}
      * role on the device.
      *
+     * This functionality is only available when
+     * {@link CarrierConfigManager#KEY_SUPPORTS_CALL_COMPOSER_BOOL} is set to {@code true} in the
+     * bundle returned from {@link #getCarrierConfig()}.
+     *
      * @param pictureToUpload An {@link InputStream} that supplies the bytes representing the
      *                        picture to upload. The client bears responsibility for closing this
      *                        stream after {@code callback} is called with success or failure.
@@ -4469,7 +4473,9 @@
      *                        of {@link #getMaximumCallComposerPictureSize()}, the upload will be
      *                        aborted and the callback will be called with an exception containing
      *                        {@link CallComposerException#ERROR_FILE_TOO_LARGE}.
-     * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg)
+     * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg). The list
+     *                    of acceptable content types can be found at 3GPP TS 26.141 sections
+     *                    4.2 and 4.3.
      * @param executor The {@link Executor} on which the {@code pictureToUpload} stream will be
      *                 read, as well as on which the callback will be called.
      * @param callback A callback called when the upload operation terminates, either in success
@@ -8591,7 +8597,8 @@
     @IntDef({
             ALLOWED_NETWORK_TYPES_REASON_USER,
             ALLOWED_NETWORK_TYPES_REASON_POWER,
-            ALLOWED_NETWORK_TYPES_REASON_CARRIER
+            ALLOWED_NETWORK_TYPES_REASON_CARRIER,
+            ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AllowedNetworkTypesReason {
@@ -8628,6 +8635,14 @@
     public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2;
 
     /**
+     * To indicate allowed network type change is requested by the user via the 2G toggle.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
+
+    /**
      * Set the allowed network types of the device and
      * provide the reason triggering the allowed network change.
      * This can be called for following reasons
@@ -8636,6 +8651,8 @@
      * <li>Allowed network types control by power manager
      * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER}
      * <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
+     * <li>Allowed network types control by the user-controlled "Allow 2G" toggle
+     * {@link #ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}
      * </ol>
      * This API will result in allowing an intersection of allowed network types for all reasons,
      * including the configuration done through other reasons.
@@ -8719,6 +8736,7 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                 return true;
         }
         return false;
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 7773c0a..38b551b 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -35,6 +35,7 @@
 import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -52,6 +53,16 @@
 
     private static final int ENCODING_VERSION = 1;
 
+    /**
+     * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
+     */
+    private static final String DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES = ":";
+
+    /**
+     * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
+     */
+    private static final String DELIMITER_INDIVIDUAL_PACKAGE_NAMES = ",";
+
     public static final @android.annotation.NonNull Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
         @Override
         public UiccAccessRule createFromParcel(Parcel in) {
@@ -98,6 +109,36 @@
     }
 
     /**
+     * Decodes {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY} values.
+     * @hide
+     */
+    @Nullable
+    public static UiccAccessRule[] decodeRulesFromCarrierConfig(@Nullable String[] certs) {
+        if (certs == null) {
+            return null;
+        }
+        List<UiccAccessRule> carrierConfigAccessRulesArray = new ArrayList();
+        for (String cert : certs) {
+            String[] splitStr = cert.split(DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES);
+            byte[] certificateHash = IccUtils.hexStringToBytes(splitStr[0]);
+            if (splitStr.length == 1) {
+                // The value is a certificate hash, without any package name
+                carrierConfigAccessRulesArray.add(new UiccAccessRule(certificateHash, null, 0));
+            } else {
+                // The value is composed of the certificate hash followed by at least one
+                // package name
+                String[] packageNames = splitStr[1].split(DELIMITER_INDIVIDUAL_PACKAGE_NAMES);
+                for (String packageName : packageNames) {
+                    carrierConfigAccessRulesArray.add(
+                            new UiccAccessRule(certificateHash, packageName, 0));
+                }
+            }
+        }
+        return carrierConfigAccessRulesArray.toArray(
+            new UiccAccessRule[carrierConfigAccessRulesArray.size()]);
+    }
+
+    /**
      * Decodes a byte array generated with {@link #encodeRules}.
      * @hide
      */
@@ -222,6 +263,14 @@
         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
     }
 
+    /**
+     * Returns true if the given certificate and package name match this rule's values.
+     * @hide
+     */
+    public boolean matches(@Nullable String certHash, @Nullable String packageName) {
+        return matches(IccUtils.hexStringToBytes(certHash), packageName);
+    }
+
     private boolean matches(byte[] certHash, String packageName) {
         return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
                 (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 070fd799..09c07d3 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.RcsFeature;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -467,7 +468,7 @@
      * poll on the network unless there are contacts being queried with stale information.
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
      * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
@@ -484,7 +485,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestCapabilities(@NonNull List<Uri> contactNumbers,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
@@ -557,7 +559,7 @@
      *
      * <p>
      * Be sure to check the availability of this feature using
-     * {@link ImsRcsManager#isAvailable(int)} and ensuring
+     * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
      * enabled or else this operation will fail with
@@ -571,7 +573,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+            Manifest.permission.READ_CONTACTS})
     public void requestAvailability(@NonNull Uri contactNumber,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index 4435640e..a217d13 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.telephony.ims.ImsException;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.util.Log;
@@ -47,7 +48,7 @@
      * Receives the request of publishing capabilities from the network and deliver this request
      * to the framework via the registered capability exchange event listener.
      */
-    public void onRequestPublishCapabilities(int publishTriggerType) {
+    public void onRequestPublishCapabilities(int publishTriggerType) throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -56,13 +57,15 @@
             listener.onRequestPublishCapabilities(publishTriggerType);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "request publish capabilities exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
     /**
      * Receives the unpublish notification and deliver this callback to the framework.
      */
-    public void onUnpublish() {
+    public void onUnpublish() throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -71,6 +74,8 @@
             listener.onUnpublish();
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Unpublish exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -79,7 +84,8 @@
      * request to the framework.
      */
     public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
-            @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback) {
+            @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+            throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
             return;
@@ -87,10 +93,11 @@
 
         IOptionsRequestCallback internalCallback = new IOptionsRequestCallback.Stub() {
             @Override
-            public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities) {
+            public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities,
+                    boolean isBlocked) {
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    callback.onRespondToCapabilityRequest(ownCapabilities);
+                    callback.onRespondToCapabilityRequest(ownCapabilities, isBlocked);
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -110,6 +117,8 @@
             listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote capability request exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
index d4d5301..8eecbca 100644
--- a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
@@ -27,8 +27,9 @@
      * Respond to a remote capability request from the contact specified with the capabilities
      * of this device.
      * @param ownCapabilities The capabilities of this device.
+     * @param isBlocked True if the user has blocked the number sending this request.
      */
-    void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities);
+    void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities, boolean isBlocked);
 
     /**
      * Respond to a remote capability request from the contact specified with the
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index d9734a7..4967e5d 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -16,32 +16,58 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.net.Uri;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.RcsFeature;
+import android.util.Log;
+
+import java.util.List;
 
 /**
- * The interface of the capabilities event listener for ImsService to notify the framework of the
- * UCE request and status updated.
+ * The interface that is used by the framework to listen to events from the vendor RCS stack
+ * regarding capabilities exchange using presence server and OPTIONS.
  * @hide
  */
 @SystemApi
 public interface CapabilityExchangeEventListener {
     /**
      * Interface used by the framework to respond to OPTIONS requests.
-     * @hide
      */
     interface OptionsRequestCallback {
         /**
          * Respond to a remote capability request from the contact specified with the
          * capabilities of this device.
          * @param ownCapabilities The capabilities of this device.
+         * @hide
          */
-        void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities);
+        default void onRespondToCapabilityRequest(
+                @NonNull RcsContactUceCapability ownCapabilities) {}
+
+        /**
+         * Respond to a remote capability request from the contact specified with the
+         * capabilities of this device.
+         * @param ownCapabilities The capabilities of this device.
+         * @param isBlocked Whether or not the user has blocked the number requesting the
+         *         capabilities of this device. If true, the device should respond to the OPTIONS
+         *         request with a 200 OK response and no capabilities.
+         */
+        default void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities,
+                boolean isBlocked) {
+            Log.w("CapabilityExchangeEventListener", "implement "
+                    + "onRespondToCapabilityRequest(RcsContactUceCapability, boolean) instead!");
+            // Fall back to old implementation
+            if (isBlocked) {
+                onRespondToCapabilityRequestWithError(200, "OK");
+            } else {
+                onRespondToCapabilityRequest(ownCapabilities);
+            }
+        }
 
         /**
          * Respond to a remote capability request from the contact specified with the
@@ -49,7 +75,8 @@
          * @param code The SIP response code to respond with.
          * @param reason A non-null String containing the reason associated with the SIP code.
          */
-        void onRespondToCapabilityRequestWithError(int code, @NonNull String reason);
+        void onRespondToCapabilityRequestWithError(@IntRange(from = 100, to = 699) int code,
+                @NonNull String reason);
     }
 
     /**
@@ -59,8 +86,7 @@
      * This is typically used when trying to generate an initial PUBLISH for a new subscription to
      * the network. The device will cache all presence publications after boot until this method is
      * called the first time.
-     * @param publishTriggerType {@link RcsUceAdapter#StackPublishTriggerType} The reason for the
-     * capability update request.
+     * @param publishTriggerType The reason for the capability update request.
      * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not currently
      * connected to the framework. This can happen if the {@link RcsFeature} is not
      * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
@@ -81,4 +107,25 @@
      * Telephony stack has crashed.
      */
     void onUnpublish() throws ImsException;
+
+    /**
+     * Inform the framework of an OPTIONS query from a remote device for this device's UCE
+     * capabilities.
+     * <p>
+     * The framework will respond via the
+     * {@link OptionsRequestCallback#onRespondToCapabilityRequest} or
+     * {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
+     * @param contactUri The URI associated with the remote contact that is
+     * requesting capabilities.
+     * @param remoteCapabilities The remote contact's capability information.
+     * @param callback The callback of this request which is sent from the remote user.
+     * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
+     * currently connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+     * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
+     * cases when the Telephony stack has crashed.
+     */
+    void onRemoteCapabilityRequest(@NonNull Uri contactUri,
+            @NonNull List<String> remoteCapabilities,
+            @NonNull OptionsRequestCallback callback) throws ImsException;
 }
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index ec98be6..908869b 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.net.Uri;
@@ -141,7 +140,7 @@
          * {@link #publishCapabilities(String, PublishResponseCallback)}.
          *
          * If this network response also contains a “Reason” header, then the
-         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         * {@link #onNetworkResponse(int, String, int, String)} method should be used instead.
          *
          * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
@@ -160,7 +159,7 @@
 
         /**
          * Provide the framework with a subsequent network response update to
-         * {@link #publishCapabilities(RcsContactUceCapability, int)} that also
+         * {@link #publishCapabilities(String, PublishResponseCallback)} that also
          * includes a reason provided in the “reason” header. See RFC3326 for more
          * information.
          *
@@ -186,7 +185,6 @@
 
     /**
      * Interface used by the framework to respond to OPTIONS requests.
-     * @hide
      */
     public interface OptionsResponseCallback {
         /**
@@ -217,7 +215,7 @@
          * cases when the Telephony stack has crashed.
          */
         void onNetworkResponse(int sipCode, @NonNull String reason,
-                @Nullable List<String> theirCaps) throws ImsException;
+                @NonNull List<String> theirCaps) throws ImsException;
     }
 
     /**
@@ -243,7 +241,7 @@
 
         /**
          * Notify the framework of the response to the SUBSCRIBE request from
-         * {@link #subscribeForCapabilities(List<Uri>, SubscribeResponseCallback)}.
+         * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)}.
          * <p>
          * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the
          * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate},
@@ -251,7 +249,7 @@
          * subsequent NOTIFY responses to the subscription.
          *
          * If this network response also contains a “Reason” header, then the
-         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         * {@link #onNetworkResponse(int, String, int, String)} method should be used instead.
          *
          * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
@@ -268,7 +266,7 @@
 
         /**
          * Notify the framework  of the response to the SUBSCRIBE request from
-         * {@link #subscribeForCapabilities(RcsContactUceCapability, int)} that also
+         * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)} that also
          * includes a reason provided in the “reason” header. See RFC3326 for more
          * information.
          *
@@ -294,7 +292,8 @@
         /**
          * Notify the framework of the latest XML PIDF documents included in the network response
          * for the requested contacts' capabilities requested by the Framework using
-         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}.
+         * {@link RcsUceAdapter#requestCapabilities(List, Executor,
+         * RcsUceAdapter.CapabilitiesCallback)}.
          * <p>
          * The expected format for the PIDF XML is defined in RFC3861. Each XML document must be a
          * "application/pidf+xml" object and start with a root <presence> element. For NOTIFY
@@ -336,7 +335,8 @@
 
         /**
          * The subscription associated with a previous
-         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}
+         * {@link RcsUceAdapter#requestCapabilities(List, Executor,
+         * RcsUceAdapter.CapabilitiesCallback)}
          * operation has been terminated. This will mostly be due to the network sending a final
          * NOTIFY response due to the subscription expiring, but this may also happen due to a
          * network error.
@@ -427,12 +427,11 @@
      * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
      * in order to receive the capabilities of the remote user in response.
      * <p>
-     * The implementer must call {@link #onNetworkResponse} to send the response of this
-     * query back to the framework.
+     * The implementer must use {@link OptionsResponseCallback} to send the response of
+     * this query from the network back to the framework.
      * @param contactUri The URI of the remote user that we wish to get the capabilities of.
      * @param myCapabilities The capabilities of this device to send to the remote user.
      * @param callback The callback of this request which is sent from the remote user.
-     * @hide
      */
     // executor used is defined in the constructor.
     @SuppressLint("ExecutorRegistration")
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 6fbde50..15d19a4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -125,6 +125,7 @@
     public static final String PROVISIONING_URL_KEY = "provisioningUrl";
     public static final String BANDWIDTH_SOURCE_MODEM_KEY = "modem";
     public static final String BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY = "carrier_config";
+    public static final String BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY = "bandwidth_estimator";
     public static final String RAT_NAME_LTE = "LTE";
     public static final String RAT_NAME_NR_NSA = "NR_NSA";
     public static final String RAT_NAME_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index d79225f..ec12040 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -28,6 +29,7 @@
 import com.android.telephony.Rlog;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -253,13 +255,48 @@
         }
 
         if ((b & 0x0f) <= 0x09) {
-            ret +=  (b & 0xf);
+            ret += (b & 0xf);
         }
 
         return ret;
     }
 
     /**
+     * Encodes a string to be formatted like the EF[ADN] alpha identifier.
+     *
+     * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on
+     * the relevant specs.
+     *
+     * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if
+     * there are characters that are not supported by it.
+     *
+     * @return the encoded string including the prefix byte necessary to identify the encoding.
+     * @see #adnStringFieldToString(byte[], int, int)
+     */
+    @NonNull
+    public static byte[] stringToAdnStringField(@NonNull String alphaTag) {
+        int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0);
+        if (septets != -1) {
+            byte[] ret = new byte[septets];
+            GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length);
+            return ret;
+        }
+
+        // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to
+        // validate that the string contains only valid UCS-2 characters. Since the read path
+        // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine
+        // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's
+        // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction.
+        byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE);
+        byte[] ret = new byte[alphaTagBytes.length + 1];
+        // 0x80 tags the remaining bytes as UCS-2
+        ret[0] = (byte) 0x80;
+        System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length);
+
+        return ret;
+    }
+
+    /**
      * Decodes a string field that's formatted like the EF[ADN] alpha
      * identifier
      *
@@ -309,7 +346,7 @@
                     ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
                 } catch (UnsupportedEncodingException ex) {
                     Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
-                          ex);
+                            ex);
                 }
 
                 if (ret != null) {
@@ -342,7 +379,7 @@
                 len = length - 4;
 
             base = (char) (((data[offset + 2] & 0xFF) << 8) |
-                            (data[offset + 3] & 0xFF));
+                    (data[offset + 3] & 0xFF));
             offset += 4;
             isucs2 = true;
         }
@@ -366,7 +403,7 @@
                     count++;
 
                 ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
-                           offset, count));
+                        offset, count));
 
                 offset += count;
                 len -= count;
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/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING
index a5c4479..7fbf426 100644
--- a/tests/UpdatableSystemFontTest/TEST_MAPPING
+++ b/tests/UpdatableSystemFontTest/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "UpdatableSystemFontTest"
     }
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/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c5e76a9..1358410 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -66,6 +66,8 @@
 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_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
@@ -169,6 +171,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -192,6 +195,7 @@
 import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkTestResultParcelable;
+import android.net.OemNetworkPreferences;
 import android.net.ProxyInfo;
 import android.net.QosCallbackException;
 import android.net.QosFilter;
@@ -300,6 +304,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -359,6 +364,7 @@
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
     private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+    private static final int TEST_PACKAGE_UID = 123;
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -418,6 +424,7 @@
     @Mock EthernetManager mEthernetManager;
     @Mock NetworkPolicyManager mNetworkPolicyManager;
     @Mock KeyStore mKeyStore;
+    @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -2754,10 +2761,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(),
@@ -4117,7 +4120,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);
@@ -6021,7 +6023,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)
@@ -6030,7 +6031,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 */);
@@ -7718,13 +7718,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);
@@ -9423,4 +9429,264 @@
         }
         fail("TOO_MANY_REQUESTS never thrown");
     }
+
+    private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
+            throws PackageManager.NameNotFoundException {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    private void mockHasSystemFeature(@NonNull final String featureName,
+            @NonNull final boolean hasFeature) {
+        when(mPackageManager.hasSystemFeature(eq(featureName)))
+                .thenReturn(hasFeature);
+    }
+
+    private UidRange getNriFirstUidRange(
+            @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+        return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
+    }
+
+    private OemNetworkPreferences createDefaultOemNetworkPreferences(
+            @OemNetworkPreferences.OemNetworkPreference final int preference)
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        return new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, preference)
+                .build();
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError()
+            throws PackageManager.NameNotFoundException {
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        assertThrows(IllegalArgumentException.class,
+                () -> mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest)));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaid()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 3;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertTrue(mRequests.get(2).isRequest());
+        assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities(
+                mRequests.get(2).networkCapabilities));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 2;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+        assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertNotNull(nris);
+        assertEquals(expectedNumOfNris, nris.size());
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCorrectlySetsUids()
+            throws PackageManager.NameNotFoundException {
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final List<ConnectivityService.NetworkRequestInfo> nris =
+                new ArrayList<>(
+                        mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
+                                pref));
+
+        // Sort by uid to access nris by index
+        nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start));
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start);
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop);
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference()
+            throws PackageManager.NameNotFoundException {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfAppUids = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfAppUids,
+                nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size());
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(NullPointerException.class,
+                () -> mService.setOemNetworkPreference(
+                        null,
+                        null));
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceFailsForNonAutomotive()
+            throws PackageManager.NameNotFoundException, RemoteException {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(UnsupportedOperationException.class,
+                () -> mService.setOemNetworkPreference(
+                        createDefaultOemNetworkPreferences(networkPref),
+                        mOnSetOemNetworkPreferenceListener));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 3556c72..8f5ae97 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -89,8 +89,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PermissionMonitorTest {
-    private static final int MOCK_USER1 = 0;
-    private static final int MOCK_USER2 = 1;
+    private static final UserHandle MOCK_USER1 = UserHandle.of(0);
+    private static final UserHandle MOCK_USER2 = UserHandle.of(1);
     private static final int MOCK_UID1 = 10001;
     private static final int MOCK_UID2 = 10086;
     private static final int SYSTEM_UID1 = 1000;
@@ -123,10 +123,7 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mUserManager.getUserHandles(eq(true))).thenReturn(
-                Arrays.asList(new UserHandle[] {
-                        new UserHandle(MOCK_USER1),
-                        new UserHandle(MOCK_USER2),
-                }));
+                Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 }));
 
         mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
 
@@ -184,7 +181,8 @@
         return packageInfo;
     }
 
-    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid,
+            UserHandle user) {
         final PackageInfo pkgInfo;
         if (hasSystemPermission) {
             pkgInfo = systemPackageInfoWithPermissions(
@@ -192,7 +190,7 @@
         } else {
             pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
         }
-        pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+        pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid));
         return pkgInfo;
     }
 
@@ -382,8 +380,8 @@
             }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
         }
 
-        public void expectPermission(Boolean permission, int[] users, int[] apps) {
-            for (final int user : users) {
+        public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) {
+            for (final UserHandle user : users) {
                 for (final int app : apps) {
                     final int uid = UserHandle.getUid(user, app);
                     if (!mApps.containsKey(uid)) {
@@ -396,8 +394,8 @@
             }
         }
 
-        public void expectNoPermission(int[] users, int[] apps) {
-            for (final int user : users) {
+        public void expectNoPermission(UserHandle[] users, int[] apps) {
+            for (final UserHandle user : users) {
                 for (final int app : apps) {
                     final int uid = UserHandle.getUid(user, app);
                     if (mApps.containsKey(uid)) {
@@ -425,46 +423,48 @@
 
         // Add SYSTEM_PACKAGE2, expect only have network permission.
         mPermissionMonitor.onUserAdded(MOCK_USER1);
-        addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+        addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
 
         // Add SYSTEM_PACKAGE1, expect permission escalate.
-        addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+        addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
 
         mPermissionMonitor.onUserAdded(MOCK_USER2);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
 
-        addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+        addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{MOCK_UID1});
 
         // Remove MOCK_UID1, expect no permission left for all user.
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+        removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+                new int[]{MOCK_UID1});
 
         // Remove SYSTEM_PACKAGE1, expect permission downgrade.
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
-        removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+        removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+                SYSTEM_PACKAGE1, SYSTEM_UID);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
 
         mPermissionMonitor.onUserRemoved(MOCK_USER1);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID});
 
         // Remove all packages, expect no permission left.
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
-        removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+        removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID, MOCK_UID1});
 
         // Remove last user, expect no redundant clearPermission is invoked.
         mPermissionMonitor.onUserRemoved(MOCK_USER2);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID, MOCK_UID1});
     }
 
@@ -548,14 +548,14 @@
     // Normal package add/remove operations will trigger multiple intent for uids corresponding to
     // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
     // called multiple times with the uid corresponding to each user.
-    private void addPackageForUsers(int[] users, String packageName, int uid) {
-        for (final int user : users) {
+    private void addPackageForUsers(UserHandle[] users, String packageName, int uid) {
+        for (final UserHandle user : users) {
             mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid));
         }
     }
 
-    private void removePackageForUsers(int[] users, String packageName, int uid) {
-        for (final int user : users) {
+    private void removePackageForUsers(UserHandle[] users, String packageName, int uid) {
+        for (final UserHandle user : users) {
             mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid));
         }
     }
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index c04ddd7..1dedc19 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -21,7 +21,6 @@
         "services.core",
     ],
     libs: [
-        "android.net.ipsec.ike.stubs.module_lib",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
diff --git a/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java
new file mode 100644
index 0000000..36f5e41
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.vcn;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+
+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 VcnControlPlaneIkeConfigTest {
+    private static final IkeSessionParams IKE_PARAMS;
+    private static final TunnelModeChildSessionParams CHILD_PARAMS;
+
+    static {
+        IkeSaProposal ikeProposal =
+                new IkeSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .addDhGroup(DH_GROUP_2048_BIT_MODP)
+                        .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+                        .build();
+
+        Context mockContext = mock(Context.class);
+        ConnectivityManager mockConnectManager = mock(ConnectivityManager.class);
+        doReturn(mockConnectManager)
+                .when(mockContext)
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        doReturn(mock(Network.class)).when(mockConnectManager).getActiveNetwork();
+
+        final String serverHostname = "192.0.2.100";
+        final String testLocalId = "test.client.com";
+        final String testRemoteId = "test.server.com";
+        final byte[] psk = "psk".getBytes();
+
+        IKE_PARAMS =
+                new IkeSessionParams.Builder(mockContext)
+                        .setServerHostname(serverHostname)
+                        .addSaProposal(ikeProposal)
+                        .setLocalIdentification(new IkeFqdnIdentification(testLocalId))
+                        .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId))
+                        .setAuthPsk(psk)
+                        .build();
+
+        ChildSaProposal childProposal =
+                new ChildSaProposal.Builder()
+                        .addEncryptionAlgorithm(
+                                ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+                        .build();
+        CHILD_PARAMS =
+                new TunnelModeChildSessionParams.Builder().addSaProposal(childProposal).build();
+    }
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnControlPlaneIkeConfig buildTestConfig() {
+        return new VcnControlPlaneIkeConfig(IKE_PARAMS, CHILD_PARAMS);
+    }
+
+    @Test
+    public void testGetters() {
+        final VcnControlPlaneIkeConfig config = buildTestConfig();
+        assertEquals(IKE_PARAMS, config.getIkeSessionParams());
+        assertEquals(CHILD_PARAMS, config.getChildSessionParams());
+    }
+
+    @Test
+    public void testConstructConfigWithoutIkeParams() {
+        try {
+            new VcnControlPlaneIkeConfig(null, CHILD_PARAMS);
+            fail("Expect to fail because ikeParams was null");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @Test
+    public void testBuilderConfigWithoutChildParams() {
+        try {
+            new VcnControlPlaneIkeConfig(IKE_PARAMS, null);
+            fail("Expect to fail because childParams was null");
+        } catch (NullPointerException expected) {
+        }
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 3e659d0..5b17aad 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 
 import android.net.NetworkCapabilities;
@@ -57,17 +58,22 @@
             };
     public static final int MAX_MTU = 1360;
 
+    public static final VcnControlPlaneConfig CONTROL_PLANE_CONFIG =
+            VcnControlPlaneIkeConfigTest.buildTestConfig();
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
         return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
     }
 
+    private static VcnGatewayConnectionConfig.Builder newBuilder() {
+        return new VcnGatewayConnectionConfig.Builder(CONTROL_PLANE_CONFIG);
+    }
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
         final VcnGatewayConnectionConfig.Builder builder =
-                new VcnGatewayConnectionConfig.Builder()
-                        .setRetryInterval(RETRY_INTERVALS_MS)
-                        .setMaxMtu(MAX_MTU);
+                newBuilder().setRetryInterval(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU);
 
         for (int caps : exposedCaps) {
             builder.addExposedCapability(caps);
@@ -81,9 +87,19 @@
     }
 
     @Test
+    public void testBuilderRequiresNonNullControlPlaneConfig() {
+        try {
+            new VcnGatewayConnectionConfig.Builder(null).build();
+
+            fail("Expected exception due to invalid control plane config");
+        } catch (NullPointerException e) {
+        }
+    }
+
+    @Test
     public void testBuilderRequiresNonEmptyExposedCaps() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
+            newBuilder()
                     .addRequiredUnderlyingCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                     .build();
 
@@ -95,9 +111,7 @@
     @Test
     public void testBuilderRequiresNonEmptyUnderlyingCaps() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
-                    .addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .build();
+            newBuilder().addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
 
             fail("Expected exception due to invalid required underlying capabilities");
         } catch (IllegalArgumentException e) {
@@ -107,7 +121,7 @@
     @Test
     public void testBuilderRequiresNonNullRetryInterval() {
         try {
-            new VcnGatewayConnectionConfig.Builder().setRetryInterval(null);
+            newBuilder().setRetryInterval(null);
             fail("Expected exception due to invalid retryIntervalMs");
         } catch (IllegalArgumentException e) {
         }
@@ -116,7 +130,7 @@
     @Test
     public void testBuilderRequiresNonEmptyRetryInterval() {
         try {
-            new VcnGatewayConnectionConfig.Builder().setRetryInterval(new long[0]);
+            newBuilder().setRetryInterval(new long[0]);
             fail("Expected exception due to invalid retryIntervalMs");
         } catch (IllegalArgumentException e) {
         }
@@ -125,8 +139,7 @@
     @Test
     public void testBuilderRequiresValidMtu() {
         try {
-            new VcnGatewayConnectionConfig.Builder()
-                    .setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1);
+            newBuilder().setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1);
             fail("Expected exception due to invalid mtu");
         } catch (IllegalArgumentException e) {
         }
@@ -144,6 +157,9 @@
         Arrays.sort(underlyingCaps);
         assertArrayEquals(UNDERLYING_CAPS, underlyingCaps);
 
+        assertEquals(CONTROL_PLANE_CONFIG, config.getControlPlaneConfig());
+        assertFalse(CONTROL_PLANE_CONFIG == config.getControlPlaneConfig());
+
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs());
         assertEquals(MAX_MTU, config.getMaxMtu());
     }
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7dada9d..7087676 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -22,28 +22,40 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.any;
 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 static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager.VcnStatusCallback;
+import android.net.vcn.VcnManager.VcnStatusCallbackBinder;
 import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
+import android.os.ParcelUuid;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.net.UnknownHostException;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 
 public class VcnManagerTest {
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+    private static final int[] UNDERLYING_NETWORK_CAPABILITIES = {
+        NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_INTERNET
+    };
     private static final Executor INLINE_EXECUTOR = Runnable::run;
 
     private IVcnManagementService mMockVcnManagementService;
     private VcnUnderlyingNetworkPolicyListener mMockPolicyListener;
+    private VcnStatusCallback mMockStatusCallback;
 
     private Context mContext;
     private VcnManager mVcnManager;
@@ -52,6 +64,7 @@
     public void setUp() {
         mMockVcnManagementService = mock(IVcnManagementService.class);
         mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class);
+        mMockStatusCallback = mock(VcnStatusCallback.class);
 
         mContext = getContext();
         mVcnManager = new VcnManager(mContext, mMockVcnManagementService);
@@ -132,4 +145,74 @@
     public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception {
         mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null);
     }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        verify(mMockVcnManagementService)
+                .registerVcnStatusCallback(eq(SUB_GROUP), notNull(), any());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception {
+        mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullExecutor() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testRegisterVcnStatusCallbackNullCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() throws Exception {
+        mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verify(mMockVcnManagementService).unregisterVcnStatusCallback(any());
+    }
+
+    @Test
+    public void testUnregisterUnknownVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        verifyNoMoreInteractions(mMockVcnManagementService);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testUnregisterNullVcnStatusCallback() throws Exception {
+        mVcnManager.unregisterVcnStatusCallback(null);
+    }
+
+    @Test
+    public void testVcnStatusCallbackBinder() throws Exception {
+        IVcnStatusCallback cbBinder =
+                new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
+
+        cbBinder.onEnteredSafeMode();
+        verify(mMockStatusCallback).onEnteredSafeMode();
+
+        cbBinder.onGatewayConnectionError(
+                UNDERLYING_NETWORK_CAPABILITIES,
+                VcnManager.VCN_ERROR_CODE_NETWORK_ERROR,
+                "java.net.UnknownHostException",
+                "exception_message");
+        verify(mMockStatusCallback)
+                .onGatewayConnectionError(
+                        eq(UNDERLYING_NETWORK_CAPABILITIES),
+                        eq(VcnManager.VCN_ERROR_CODE_NETWORK_ERROR),
+                        any(UnknownHostException.class));
+    }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java
new file mode 100644
index 0000000..2110d6e
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.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.net.vcn;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.TelephonyNetworkSpecifier;
+
+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 VcnUnderlyingNetworkSpecifierTest {
+    private static final int[] TEST_SUB_IDS = new int[] {1, 2, 3, 5};
+
+    @Test
+    public void testGetSubIds() {
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+
+        assertEquals(TEST_SUB_IDS, specifier.getSubIds());
+    }
+
+    @Test
+    public void testParceling() {
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+        assertParcelSane(specifier, 1);
+    }
+
+    @Test
+    public void testCanBeSatisfiedByTelephonyNetworkSpecifier() {
+        final TelephonyNetworkSpecifier telSpecifier =
+                new TelephonyNetworkSpecifier(TEST_SUB_IDS[0]);
+
+        final VcnUnderlyingNetworkSpecifier specifier =
+                new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS);
+        assertTrue(specifier.canBeSatisfiedBy(telSpecifier));
+    }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index c290bff..45b2381 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -26,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -42,9 +43,11 @@
 import static org.mockito.Mockito.eq;
 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.when;
 
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -52,6 +55,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkCapabilities.Transport;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
@@ -70,7 +74,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.internal.util.LocationPermissionChecker;
+import com.android.server.VcnManagementService.VcnCallback;
+import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
@@ -147,14 +153,17 @@
             mock(PersistableBundleUtils.LockingReadWriteHelper.class);
     private final TelephonySubscriptionTracker mSubscriptionTracker =
             mock(TelephonySubscriptionTracker.class);
+    private final LocationPermissionChecker mLocationPermissionChecker =
+            mock(LocationPermissionChecker.class);
 
-    private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
-            ArgumentCaptor.forClass(VcnSafemodeCallback.class);
+    private final ArgumentCaptor<VcnCallback> mVcnCallbackCaptor =
+            ArgumentCaptor.forClass(VcnCallback.class);
 
     private final VcnManagementService mVcnMgmtSvc;
 
     private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
             mock(IVcnUnderlyingNetworkPolicyListener.class);
+    private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class);
     private final IBinder mMockIBinder = mock(IBinder.class);
 
     public VcnManagementServiceTest() throws Exception {
@@ -171,6 +180,7 @@
 
         doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
+        doReturn(mMockContext).when(mVcnContext).getContext();
         doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
         doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
         doReturn(mVcnContext)
@@ -188,6 +198,9 @@
         doReturn(mConfigReadWriteHelper)
                 .when(mMockDeps)
                 .newPersistableBundleLockingReadWriteHelper(any());
+        doReturn(mLocationPermissionChecker)
+                .when(mMockDeps)
+                .newLocationPermissionChecker(eq(mMockContext));
 
         // Setup VCN instance generation
         doAnswer((invocation) -> {
@@ -206,6 +219,7 @@
         mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
 
         doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();
+        doReturn(mMockIBinder).when(mMockStatusCallback).asBinder();
 
         // Make sure the profiles are loaded.
         mTestLooper.dispatchAll();
@@ -707,24 +721,138 @@
         verify(mMockPolicyListener).onPolicyChanged();
     }
 
-    @Test
-    public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
-        TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+    private void verifyVcnCallback(
+            @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
+            throws Exception {
         verify(mMockDeps)
                 .newVcn(
                         eq(mVcnContext),
-                        eq(TEST_UUID_1),
+                        eq(subGroup),
                         eq(TEST_VCN_CONFIG),
                         eq(snapshot),
-                        mSafemodeCallbackCaptor.capture());
+                        mVcnCallbackCaptor.capture());
 
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
-        safemodeCallback.onEnteredSafemode();
+        VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
+        vcnCallback.onEnteredSafeMode();
 
-        assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
         verify(mMockPolicyListener).onPolicyChanged();
     }
+
+    @Test
+    public void testVcnCallbackOnEnteredSafeMode() throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+
+        verifyVcnCallback(TEST_UUID_1, snapshot);
+    }
+
+    private void triggerVcnStatusCallbackOnEnteredSafeMode(
+            @NonNull ParcelUuid subGroup,
+            @NonNull String pkgName,
+            int uid,
+            boolean hasPermissionsforSubGroup,
+            boolean hasLocationPermission)
+            throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+
+        doReturn(hasPermissionsforSubGroup)
+                .when(snapshot)
+                .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
+
+        doReturn(hasLocationPermission)
+                .when(mLocationPermissionChecker)
+                .checkLocationPermission(eq(pkgName), any(), eq(uid), any());
+
+        mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
+
+        // Trigger systemReady() to set up LocationPermissionChecker
+        mVcnMgmtSvc.systemReady();
+
+        verifyVcnCallback(subGroup, snapshot);
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, times(1)).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                false /* hasPermissionsforSubGroup */,
+                true /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception {
+        triggerVcnStatusCallbackOnEnteredSafeMode(
+                TEST_UUID_1,
+                TEST_PACKAGE_NAME,
+                TEST_UID,
+                true /* hasPermissionsforSubGroup */,
+                false /* hasLocationPermission */);
+
+        verify(mMockStatusCallback, never()).onEnteredSafeMode();
+    }
+
+    @Test
+    public void testRegisterVcnStatusCallback() throws Exception {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        assertNotNull(cbInfo);
+        assertEquals(TEST_UUID_1, cbInfo.mSubGroup);
+        assertEquals(mMockStatusCallback, cbInfo.mCallback);
+        assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
+        assertEquals(TEST_UID, cbInfo.mUid);
+        verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegisterVcnStatusCallbackDuplicate() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallback() {
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+        Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+        VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+        verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testRegisterVcnStatusCallbackInvalidPackage() {
+        doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME);
+
+        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testUnregisterVcnStatusCallbackNeverRegistered() {
+        mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+
+        assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..69c21b9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -20,6 +20,9 @@
 import static android.net.IpSecManager.DIRECTION_OUT;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
 
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
@@ -34,9 +37,16 @@
 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 android.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.TemporaryFailureException;
+import android.net.vcn.VcnManager.VcnErrorCode;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,6 +56,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.io.IOException;
+import java.net.UnknownHostException;
 import java.util.Collections;
 
 /** Tests for VcnGatewayConnection.ConnectedState */
@@ -73,6 +85,11 @@
     }
 
     @Test
+    public void testEnterStateDoesNotCancelSafeModeAlarm() {
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    }
+
+    @Test
     public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
         mGatewayConnection
                 .getUnderlyingNetworkTrackerCallback()
@@ -81,6 +98,7 @@
 
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
         verify(mIkeSession, never()).close();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -120,7 +138,24 @@
     }
 
     @Test
+    public void testMigratedTransformsAreApplied() throws Exception {
+        getChildSessionCallback()
+                .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
+        mTestLooper.dispatchAll();
+
+        for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
+            verify(mIpSecSvc)
+                    .applyTunnelModeTransform(
+                            eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
+        }
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @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 +197,97 @@
         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);
+
+        // The child session was closed without exception, so verify that the GatewayStatusCallback
+        // was not notified
+        verifyNoMoreInteractions(mGatewayStatusCallback);
+    }
+
+    @Test
+    public void testChildSessionClosedExceptionallyNotifiesGatewayStatusCallback()
+            throws Exception {
+        final IkeInternalException exception = new IkeInternalException(mock(IOException.class));
+        getChildSessionCallback().onClosedExceptionally(exception);
+        mTestLooper.dispatchAll();
+
+        verify(mGatewayStatusCallback)
+                .onGatewayConnectionError(
+                        eq(mConfig.getRequiredUnderlyingCapabilities()),
+                        eq(VCN_ERROR_CODE_INTERNAL_ERROR),
+                        any(),
+                        any());
     }
 
     @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);
+
+        // IkeSession closed with no error, so verify that the GatewayStatusCallback was not
+        // notified
+        verifyNoMoreInteractions(mGatewayStatusCallback);
+    }
+
+    private void verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+            IkeException cause, @VcnErrorCode int expectedErrorType) {
+        getIkeSessionCallback().onClosedExceptionally(cause);
+        mTestLooper.dispatchAll();
+
+        verify(mIkeSession).close();
+
+        verify(mGatewayStatusCallback)
+                .onGatewayConnectionError(
+                        eq(mConfig.getRequiredUnderlyingCapabilities()),
+                        eq(expectedErrorType),
+                        any(),
+                        any());
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyAuthenticationFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new AuthenticationFailedException("vcn test"), VCN_ERROR_CODE_CONFIG_ERROR);
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyDnsFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new IkeInternalException(new UnknownHostException()), VCN_ERROR_CODE_NETWORK_ERROR);
+    }
+
+    @Test
+    public void testIkeSessionClosedExceptionallyInternalFailure() throws Exception {
+        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
+                new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d936183..17ae19e 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 16181b6..9ea641f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -48,7 +48,8 @@
                         .createIpSecTunnelInterface(
                                 DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
         mGatewayConnection.setTunnelInterface(tunnelIface);
-        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
+
+        // Don't need to transition to DisconnectedState because it is the starting state
         mTestLooper.dispatchAll();
     }
 
@@ -78,6 +79,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -88,6 +90,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -97,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..7385204 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..5b0850b 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..a660735 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());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 66cbf84..9d33682 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -34,7 +34,7 @@
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
@@ -56,7 +56,7 @@
     private VcnContext mVcnContext;
     private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     private VcnNetworkProvider mVcnNetworkProvider;
-    private VcnSafemodeCallback mVcnSafemodeCallback;
+    private VcnCallback mVcnCallback;
     private Vcn.Dependencies mDeps;
 
     private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor;
@@ -72,7 +72,7 @@
         mVcnContext = mock(VcnContext.class);
         mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
         mVcnNetworkProvider = mock(VcnNetworkProvider.class);
-        mVcnSafemodeCallback = mock(VcnSafemodeCallback.class);
+        mVcnCallback = mock(VcnCallback.class);
         mDeps = mock(Vcn.Dependencies.class);
 
         mTestLooper = new TestLooper();
@@ -104,7 +104,7 @@
                         TEST_SUB_GROUP,
                         mConfig,
                         mSubscriptionSnapshot,
-                        mVcnSafemodeCallback,
+                        mVcnCallback,
                         mDeps);
     }
 
@@ -148,7 +148,7 @@
     }
 
     @Test
-    public void testGatewayEnteringSafemodeNotifiesVcn() {
+    public void testGatewayEnteringSafeModeNotifiesVcn() {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
         for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, capability);
@@ -168,16 +168,17 @@
                         any(),
                         mGatewayStatusCallbackCaptor.capture());
 
-        // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
+        // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
         // all Gateways
         final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
-        statusCallback.onEnteredSafemode();
+        statusCallback.onEnteredSafeMode();
         mTestLooper.dispatchAll();
 
+        assertFalse(mVcn.isActive());
         for (final VcnGatewayConnection gatewayConnection : gatewayConnections) {
             verify(gatewayConnection).teardownAsynchronously();
         }
         verify(mVcnNetworkProvider).unregisterListener(requestListener);
-        verify(mVcnSafemodeCallback).onEnteredSafemode();
+        verify(mVcnCallback).onEnteredSafeMode();
     }
 }