Merge "Adding a little more data to an exception." 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/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/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 9ef3eb4..c7710fc 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";
@@ -959,6 +960,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 +1188,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
@@ -1392,6 +1396,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
@@ -1746,6 +1752,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
   }
@@ -6069,6 +6078,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 {
@@ -6958,6 +6974,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);
@@ -7085,6 +7102,7 @@
     method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
     method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
     method public boolean isUniqueDeviceAttestationSupported();
+    method public boolean isUsbDataSignalingEnabled();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
     method public void lockNow();
     method public void lockNow(int);
@@ -7186,6 +7204,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);
@@ -8302,6 +8321,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);
@@ -8309,11 +8329,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 {
@@ -8366,6 +8388,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 {
@@ -8409,6 +8432,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;
@@ -8417,6 +8442,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;
@@ -10234,12 +10261,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);
@@ -16524,9 +16554,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 {
@@ -24056,13 +24090,49 @@
 
 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 PlaybackSession implements java.lang.AutoCloseable {
     method public void close();
     method @NonNull public String getId();
+    method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent);
+  }
+
+  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);
   }
 
 }
@@ -34710,6 +34780,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";
@@ -35050,30 +35121,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();
   }
@@ -54635,6 +54694,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);
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..cec2f98 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -27,6 +27,7 @@
     field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKUP = "android.permission.BACKUP";
+    field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
     field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
     field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
@@ -343,6 +344,7 @@
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     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 +682,7 @@
   }
 
   public static class Notification.Action implements android.os.Parcelable {
+    field public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; // 0xc
     field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
   }
 
@@ -872,7 +875,6 @@
   }
 
   public class DevicePolicyManager {
-    method public boolean canAdminGrantSensorsPermissionsForUser(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1852,6 +1854,7 @@
   }
 
   public final class BluetoothDevice implements android.os.Parcelable {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
@@ -2120,6 +2123,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 +2819,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 +3544,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 +7379,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 +7394,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 +8568,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 +8638,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 {
@@ -9410,22 +9451,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 {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 29be5c6..c03f660 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
@@ -2568,6 +2570,7 @@
 
   public final class InputMethodManager {
     method public int getDisplayId();
+    method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method public boolean isInputMethodPickerShown();
   }
 
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/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/ILocalWallpaperColorConsumer.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
new file mode 100644
index 0000000..28b11ec
--- /dev/null
+++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+import android.app.WallpaperColors;
+import android.graphics.RectF;
+
+/**
+ * @hide
+ */
+oneway interface ILocalWallpaperColorConsumer {
+    void onColorsChanged(in RectF area, in WallpaperColors colors);
+}
\ No newline at end of file
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 101917b..5402381 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -17,9 +17,11 @@
 package android.app;
 
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.app.IWallpaperManagerCallback;
+import android.app.ILocalWallpaperColorConsumer;
 import android.app.WallpaperInfo;
 import android.content.ComponentName;
 import android.app.WallpaperColors;
@@ -162,6 +164,18 @@
     WallpaperColors getWallpaperColors(int which, int userId, int displayId);
 
     /**
+    * @hide
+    */
+    void removeOnLocalColorsChangedListener(
+            in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
+
+    /**
+    * @hide
+    */
+    void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
+                                    in List<RectF> regions, int which, int userId, int displayId);
+
+    /**
      * Register a callback to receive color updates from a display
      */
     void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ac2f223..d7587bd 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -66,6 +66,7 @@
 import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
@@ -107,6 +108,8 @@
     private static boolean DEBUG = false;
     private float mWallpaperXStep = -1;
     private float mWallpaperYStep = -1;
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     /** {@hide} */
     private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -309,6 +312,8 @@
         private int mCachedWallpaperUserId;
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
+        private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
+                mLocalColorCallbacks = new ArrayMap<>();
 
         Globals(IWallpaperManager service, Looper looper) {
             mService = service;
@@ -350,6 +355,40 @@
             }
         }
 
+        private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
+            ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
+                @Override
+                public void onColorsChanged(RectF area, WallpaperColors colors) {
+                    callback.onColorsChanged(area, colors);
+                }
+            };
+            mLocalColorCallbacks.put(callback, callback2);
+            return callback2;
+        }
+
+        public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+                @NonNull List<RectF> regions, int which, int userId, int displayId) {
+            try {
+                mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
+                                                         userId, displayId);
+            } catch (RemoteException e) {
+                // Can't get colors, connection lost.
+            }
+        }
+
+        public void removeOnColorsChangedListener(
+                @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
+                int displayId) {
+            ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
+            if (callback2 == null) return;
+            try {
+                mService.removeOnLocalColorsChangedListener(
+                        callback2, which, userId, displayId);
+            } catch (RemoteException e) {
+                // Can't get colors, connection lost.
+            }
+        }
+
         /**
          * Stop listening to wallpaper color events.
          *
@@ -1057,6 +1096,29 @@
     }
 
     /**
+     * @hide
+     */
+    public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+            List<RectF> regions) throws IllegalArgumentException {
+        for (RectF region : regions) {
+            if (!LOCAL_COLOR_BOUNDS.contains(region)) {
+                throw new IllegalArgumentException("Regions must be within bounds "
+                        + LOCAL_COLOR_BOUNDS);
+            }
+        }
+        sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
+                                                 mContext.getUserId(), mContext.getDisplayId());
+    }
+
+    /**
+     * @hide
+     */
+    public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+        sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
+                mContext.getDisplayId());
+    }
+
+    /**
      * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
      * for a given user.  The caller must hold the INTERACT_ACROSS_USERS_FULL
      * permission to access another user's wallpaper data.
@@ -2202,4 +2264,18 @@
             onColorsChanged(colors, which);
         }
     }
+
+    /**
+     * Callback to update a consumer with a local color change
+     * @hide
+     */
+    public interface LocalWallpaperColorConsumer {
+
+        /**
+         * Gets called when a color of an area gets updated
+         * @param area
+         * @param colors
+         */
+        void onColorsChanged(RectF area, WallpaperColors colors);
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 907abb2..28242b0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1869,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";
 
@@ -11718,8 +11718,11 @@
     }
 
     /**
-     * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
-     * control the network logging feature.
+     * Called by a device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
      *     functions are recorded while network logging is active:
@@ -11759,7 +11762,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
      * @param enabled whether network logging should be enabled or not.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner.
      * @see #setAffiliationIds
      * @see #retrieveNetworkLogs
      */
@@ -11773,14 +11776,16 @@
     }
 
     /**
-     * Return whether network logging is enabled by a device owner.
+     * Return whether network logging is enabled by a device owner or profile owner of
+     * a managed profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
      * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
      * or has MANAGE_USERS permission.
-     * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner and caller has
-     * no MANAGE_USERS permission
+     * @return {@code true} if network logging is enabled by device owner or profile owner,
+     * {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner and
+     * caller has no MANAGE_USERS permission
      */
     public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isNetworkLoggingEnabled");
@@ -11792,9 +11797,14 @@
     }
 
     /**
-     * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
-     * the most recent batch of network logging events.
-     * A device owner has to provide a batchToken provided as part of
+     * Called by device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to retrieve the most recent batch of
+     * network logging events.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
+     *
+     * A device owner or profile owner has to provide a batchToken provided as part of
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
      * token of the most recent available batch of logs, {@code null} will be returned.
      *
@@ -11806,11 +11816,11 @@
      * after the device device owner has been notified via
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
      *
-     * <p>If a secondary user or profile is created, calling this method will throw a
-     * {@link SecurityException} until all users become affiliated again. It will also no longer be
-     * possible to retrieve the network logs batch with the most recent batchToken provided
-     * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
-     * {@link DevicePolicyManager#setAffiliationIds}.
+     * <p>If the caller is not a profile owner and a secondary user or profile is created, calling
+     * this method will throw a {@link SecurityException} until all users become affiliated again.
+     * It will also no longer be possible to retrieve the network logs batch with the most recent
+     * batchToken provided by {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
+     * See {@link DevicePolicyManager#setAffiliationIds}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
@@ -11818,8 +11828,9 @@
      * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
      *        {@code null} if the batch represented by batchToken is no longer available or if
      *        logging is disabled.
-     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
-     * profile or secondary user that is not affiliated with the device.
+     * @throws SecurityException if {@code admin} is not a device owner, profile owner or if the
+     * {@code admin} is not a profile owner and there is at least one profile or secondary user
+     * that is not affiliated with the device.
      * @see #setAffiliationIds
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
      */
@@ -11938,11 +11949,12 @@
     }
 
     /**
-     * Called by the system to get the time at which the device owner last retrieved network logging
-     * events.
+     * Called by the system to get the time at which the device owner or profile owner of a
+     * managed profile last retrieved network logging events.
      *
-     * @return the time at which the device owner most recently retrieved network logging events, in
-     *         milliseconds since epoch; -1 if network logging events were never retrieved.
+     * @return the time at which the device owner or profile owner most recently retrieved network
+     *         logging events, in milliseconds since epoch; -1 if network logging events were
+     *         never retrieved.
      * @throws SecurityException if the caller is not the device owner, does not hold the
      *         MANAGE_USERS permission and is not the system.
      *
@@ -13358,26 +13370,93 @@
      */
     public boolean canAdminGrantSensorsPermissions() {
         throwIfParentInstance("canAdminGrantSensorsPermissions");
-        return canAdminGrantSensorsPermissionsForUser(myUserId());
-    }
-
-    /**
-     * Returns true if the admin can control grants of sensors-related permissions, for
-     * a given user.
-     *
-     * @hide
-     * @param userId The ID of the user to check.
-     * @return if the admin may grant these permissions, false otherwise.
-     */
-    @SystemApi
-    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
         if (mService == null) {
             return false;
         }
         try {
-            return mService.canAdminGrantSensorsPermissionsForUser(userId);
+            return mService.canAdminGrantSensorsPermissionsForUser(myUserId());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to
+     * enable or disable USB data signaling for the device. When disabled, USB data connections
+     * (except from charging functions) are prohibited.
+     *
+     * <p> This API is not supported on all devices, the caller should call
+     * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
+     * signaling is supported on the device.
+     *
+     * @param enabled whether USB data signaling should be enabled or not.
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     *         an organization-owned managed profile.
+     * @throws IllegalStateException if disabling USB data signaling is not supported or
+     *         if USB data signaling fails to be enabled/disabled.
+     */
+    public void setUsbDataSignalingEnabled(boolean enabled) {
+        throwIfParentInstance("setUsbDataSignalingEnabled");
+        if (mService != null) {
+            try {
+                mService.setUsbDataSignalingEnabled(mContext.getPackageName(), enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to return
+     * whether USB data signaling is currently enabled by the admin.
+     *
+     * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+     */
+    public boolean isUsbDataSignalingEnabled() {
+        throwIfParentInstance("isUsbDataSignalingEnabled");
+        if (mService != null) {
+            try {
+                return mService.isUsbDataSignalingEnabled(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called by the system to check whether USB data signaling is currently enabled for this user.
+     *
+     * @param userId which user to check for.
+     * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) {
+        throwIfParentInstance("isUsbDataSignalingEnabledForUser");
+        if (mService != null) {
+            try {
+                return mService.isUsbDataSignalingEnabledForUser(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether enabling or disabling USB data signaling is supported on the device.
+     *
+     * @return {@code true} if the device supports enabling and disabling USB data signaling.
+     */
+    public boolean canUsbDataSignalingBeDisabled() {
+        throwIfParentInstance("canUsbDataSignalingBeDisabled");
+        if (mService != null) {
+            try {
+                return mService.canUsbDataSignalingBeDisabled();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 032cf24..94388cf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -502,4 +502,9 @@
 
     void resetDefaultCrossProfileIntentFilters(int userId);
     boolean canAdminGrantSensorsPermissionsForUser(int userId);
+
+    void setUsbDataSignalingEnabled(String callerPackage, boolean enabled);
+    boolean isUsbDataSignalingEnabled(String callerPackage);
+    boolean isUsbDataSignalingEnabledForUser(int userId);
+    boolean canUsbDataSignalingBeDisabled();
 }
diff --git a/core/java/android/app/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/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/Context.java b/core/java/android/content/Context.java
index 025d777..10b00f2 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,
@@ -4581,6 +4583,14 @@
     public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
 
     /**
+     * Official published name of the (internal) text to speech manager service.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+    /**
      * Official published name of the content capture service.
      *
      * @hide
@@ -4742,6 +4752,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 +5746,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 +5802,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 +5848,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/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/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ce0547f..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 {}
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/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/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 43480ab..49beeb3 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -127,6 +127,20 @@
      * This function returns RESULT_SUCCESS if the message has reached the HAL, but
      * does not guarantee delivery of the message to the target nanoapp.
      *
+     * Before sending the first message to your nanoapp, it's recommended that the following
+     * operations should be performed:
+     * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is
+     *    present.
+     * 2) Validate that you have the permissions to communicate with the nanoapp by looking at
+     *    {@link NanoAppState#getNanoAppPermissions}.
+     * 3) If you don't have permissions, send an idempotent message to the nanoapp ensuring any
+     *    work your app previously may have asked it to do is stopped. This is useful if your app
+     *    restarts due to permission changes and no longer has the permissions when it is started
+     *    again.
+     * 4) If you have valid permissions, send a message to your nanoapp to resubscribe so that it's
+     *    aware you have restarted or so you can initially subscribe if this is the first time you
+     *    have sent it a message.
+     *
      * @param message the message object to send
      *
      * @return the result of sending the message defined as in ContextHubTransaction.Result
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index b31b85f..7e484dd 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -117,10 +117,11 @@
      * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
      * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
      *    At this point, any further attempts of communication between the nanoapp and the
-     *    {@link ContextHubClient} will be dropped by the contexthub along with
-     *    {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
-     *    should assume no communciation can happen again until
-     *    {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+     *    {@link ContextHubClient} will be dropped by the contexthub and a return value of
+     *    {@link ContextHubTransaction#RESULT_FAILED_PERMISSION_DENIED} will be used when calling
+     *    {@link ContextHubClient#sendMessageToNanoApp}. The {@link ContextHubClient} should assume
+     *    no communciation can happen again until {@link ContextHubManager#AUTHORIZATION_GRANTED} is
+     *    received.
      *
      * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp associated with the new
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index d11e0a9..86f77c0 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -81,7 +81,8 @@
             RESULT_FAILED_AT_HUB,
             RESULT_FAILED_TIMEOUT,
             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
-            RESULT_FAILED_HAL_UNAVAILABLE
+            RESULT_FAILED_HAL_UNAVAILABLE,
+            RESULT_FAILED_PERMISSION_DENIED
     })
     public @interface Result {}
     public static final int RESULT_SUCCESS = 0;
@@ -117,6 +118,11 @@
      * Failure mode when the Context Hub HAL was not available.
      */
     public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
+    /**
+     * Failure mode when the user of the API doesn't have the required permissions to perform the
+     * operation.
+     */
+    public static final int RESULT_FAILED_PERMISSION_DENIED = 9;
 
     /**
      * A class describing the response for a ContextHubTransaction.
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 383321b..bd40409 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -4,3 +4,6 @@
 wyattriley@google.com
 etn@google.com
 weiwa@google.com
+
+# ContextHub team
+per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index ca79901..7f07af7 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -135,6 +135,12 @@
     /* Resets the USB gadget. */
     void resetUsbGadget();
 
+    /* Set USB data on or off */
+    boolean enableUsbDataSignal(boolean enable);
+
+    /* Gets the USB Hal Version. */
+    int getUsbHalVersion();
+
     /* Get the functionfs control handle for the given function. Usb
      * descriptors will already be written, and the handle will be
      * ready to use.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 5bac481..841fd2f 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -516,6 +516,46 @@
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
 
     /**
+     * The Value for USB hal is not presented.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_NOT_SUPPORTED = -1;
+
+    /**
+     * Value for USB Hal Version v1.0.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_0 = 10;
+
+    /**
+     * Value for USB Hal Version v1.1.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_1 = 11;
+
+    /**
+     * Value for USB Hal Version v1.2.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_2 = 12;
+
+    /**
+     * Value for USB Hal Version v1.3.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_3 = 13;
+
+    /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
@@ -617,6 +657,16 @@
     })
     public @interface UsbGadgetHalVersion {}
 
+    /** @hide */
+    @IntDef(prefix = { "USB_HAL_" }, value = {
+            USB_HAL_NOT_SUPPORTED,
+            USB_HAL_V1_0,
+            USB_HAL_V1_1,
+            USB_HAL_V1_2,
+            USB_HAL_V1_3,
+    })
+    public @interface UsbHalVersion {}
+
     private final Context mContext;
     private final IUsbManager mService;
 
@@ -1076,6 +1126,26 @@
     }
 
     /**
+     * Get the Current USB Hal Version.
+     * <p>
+     * This function returns the current USB Hal Version.
+     * </p>
+     *
+     * @return a integer {@code USB_HAL_*} represent hal version.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public @UsbHalVersion int getUsbHalVersion() {
+        try {
+            return mService.getUsbHalVersion();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets the USB Gadget.
      * <p>
      * Performs USB data stack reset through USB Gadget HAL.
@@ -1095,6 +1165,28 @@
     }
 
     /**
+     * Enable/Disable the USB data signaling.
+     * <p>
+     * Enables/Disables USB data path in all the USB ports.
+     * It will force to stop or restore USB data signaling.
+     * </p>
+     *
+     * @param enable enable or disable USB data signaling
+     * @return true enable or disable USB data successfully
+     *         false if something wrong
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public boolean enableUsbDataSignal(boolean enable) {
+        try {
+            return mService.enableUsbDataSignal(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns a list of physical USB ports on the device.
      * <p>
      * This list is guaranteed to contain all dual-role USB Type C ports but it might
diff --git a/core/java/android/net/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/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/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/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..f3a7856 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) {
+            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/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
new file mode 100644
index 0000000..9d2e5b9
--- /dev/null
+++ b/core/java/android/service/wallpaper/EngineWindowPage.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.wallpaper;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * This class represents a page of a launcher page used by the wallpaper
+ * @hide
+ */
+public class EngineWindowPage {
+    private Bitmap mScreenShot;
+    private volatile long  mLastUpdateTime;
+    private Set<RectF> mCallbackAreas = new ArraySet<>();
+    private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
+
+    /** should be locked extrnally */
+    public void addArea(RectF area) {
+        mCallbackAreas.add(area);
+    }
+
+    /** should be locked extrnally */
+    public void addWallpaperColors(RectF area, WallpaperColors colors) {
+        mCallbackAreas.add(area);
+        mRectFColors.put(area, colors);
+    }
+
+    /** get screenshot bitmap */
+    public Bitmap getBitmap() {
+        if (mScreenShot == null || mScreenShot.isRecycled()) return null;
+        return mScreenShot;
+    }
+
+    /** remove callbacks for an area */
+    public void removeArea(RectF area) {
+        mCallbackAreas.remove(area);
+        mRectFColors.remove(area);
+    }
+
+    /** set the last time the screenshot was updated */
+    public void setLastUpdateTime(long lastUpdateTime) {
+        mLastUpdateTime = lastUpdateTime;
+    }
+
+    /** get last screenshot time */
+    public long getLastUpdateTime() {
+        return mLastUpdateTime;
+    }
+
+    /** get colors for an area */
+    public WallpaperColors getColors(RectF rect) {
+        return mRectFColors.get(rect);
+    }
+
+    /** set the new bitmap version */
+    public void setBitmap(Bitmap screenShot) {
+        mScreenShot = screenShot;
+    }
+
+    /** get areas of interest */
+    public Set<RectF> getAreas() {
+        return mCallbackAreas;
+    }
+
+    /** run operations on this page */
+    public synchronized void execSync(Consumer<EngineWindowPage> run) {
+        run.accept(this);
+    }
+
+    /** nullify the area color */
+    public void removeColor(RectF colorArea) {
+        mRectFColors.remove(colorArea);
+    }
+}
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f334d9d..f81ed34 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.graphics.RectF;
 import android.os.ParcelFileDescriptor;
 import android.service.wallpaper.IWallpaperEngine;
 import android.app.WallpaperColors;
@@ -28,4 +29,5 @@
     void engineShown(IWallpaperEngine engine);
     ParcelFileDescriptor setWallpaper(String name);
     void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
+    void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
 }
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 90392e6..fbb449d 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,7 +16,10 @@
 
 package android.service.wallpaper;
 
+import android.app.ILocalWallpaperColorConsumer;
+import android.app.WallpaperColors;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.os.Bundle;
 
@@ -39,4 +42,6 @@
     void destroy();
     void setZoomOut(float scale);
     void scalePreview(in Rect positionInWindow);
+    void removeLocalColorsAreas(in List<RectF> regions);
+    void addLocalColorsAreas(in List<RectF> regions);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 82e0b4a..0b447d5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,6 +23,7 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -42,6 +43,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -53,6 +55,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.view.Display;
@@ -66,6 +70,8 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.MotionEvent;
+import android.view.PixelCopy;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.View;
@@ -83,7 +89,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 
 /**
@@ -115,7 +124,12 @@
     public static final String SERVICE_META_DATA = "android.service.wallpaper";
 
     static final String TAG = "WallpaperService";
-    static final boolean DEBUG = false;
+    static final boolean DEBUG = true;
+    static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
+    private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
+    private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     private static final int DO_ATTACH = 10;
     private static final int DO_DETACH = 20;
@@ -134,6 +148,8 @@
     private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_SCALE_PREVIEW = 10110;
+    private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
+            Float.NEGATIVE_INFINITY);
 
     private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
 
@@ -158,6 +174,14 @@
      */
     public class Engine {
         IWallpaperEngineWrapper mIWallpaperEngine;
+        final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+        final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+
+        // 2D matrix [x][y] to represent a page of a portion of a window
+        EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
+        Bitmap mLastScreenshot;
+        int mLastWindowPage = -1;
+        float mLastPageOffset = 0;
 
         // Copies from mIWallpaperEngine.
         HandlerCaller mCaller;
@@ -409,8 +433,8 @@
          */
         @VisibleForTesting
         public Engine(Supplier<Long> clockFunction, Handler handler) {
-           mClockFunction = clockFunction;
-           mHandler = handler;
+            mClockFunction = clockFunction;
+            mHandler = handler;
         }
 
         /**
@@ -448,6 +472,19 @@
         }
 
         /**
+         * Return whether the wallpaper is capable of extracting local colors in a rectangle area,
+         * Must implement without calling super:
+         * {@link #addLocalColorsAreas(List)}
+         * {@link #removeLocalColorsAreas(List)}
+         * When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
+         * See {@link com.android.systemui.ImageWallpaper} for an example
+         * @hide
+         */
+        public boolean supportsLocalColorExtraction() {
+            return false;
+        }
+
+        /**
          * Returns true if this engine is running in preview mode -- that is,
          * it is being shown to the user before they select it as the actual
          * wallpaper.
@@ -691,6 +728,8 @@
                     Log.w(TAG, "Can't notify system because wallpaper connection "
                             + "was not established.");
                 }
+                resetWindowPages();
+                processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
             }
@@ -714,6 +753,28 @@
         }
 
         /**
+         * Send the changed local color areas for the connection
+         * @param regions
+         * @param colors
+         * @hide
+         */
+        public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
+                @NonNull List<WallpaperColors> colors)
+                throws RuntimeException {
+            for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
+                try {
+                    mConnection.onLocalWallpaperColorsChanged(
+                            regions.get(i),
+                            colors.get(i),
+                            mDisplayContext.getDisplayId()
+                    );
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        /**
          * Sets internal engine state. Only for testing.
          * @param created {@code true} or {@code false}.
          * @hide
@@ -1068,7 +1129,9 @@
                         mIsCreating = false;
                         mSurfaceCreated = true;
                         if (redrawNeeded) {
+                            resetWindowPages();
                             mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
+                            processLocalColors(mPendingXOffset, mPendingXOffsetStep);
                         }
                         reposition();
                         mIWallpaperEngine.reportShown();
@@ -1209,6 +1272,7 @@
             if (!mDestroyed) {
                 mVisible = visible;
                 reportVisibility();
+                if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             }
         }
 
@@ -1278,6 +1342,405 @@
                 } catch (RemoteException e) {
                 }
             }
+
+            // setup local color extraction data
+            processLocalColors(xOffset, xOffsetStep);
+        }
+
+        private void processLocalColors(float xOffset, float xOffsetStep) {
+            // implemented by the wallpaper
+            if (supportsLocalColorExtraction()) return;
+            if (DEBUG) {
+                Log.d(TAG, "processLocalColors " + xOffset + " of step "
+                        + xOffsetStep);
+            }
+            //below is the default implementation
+            if (!validStep(xOffsetStep)) {
+                if (DEBUG) {
+                    Log.w(TAG, "invalid offset step " + xOffsetStep);
+                }
+                return;
+            }
+
+            int xPage = Math.round(xOffset / xOffsetStep);
+            if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
+            mLastWindowPage = xPage;
+            mLastPageOffset = xOffset;
+            int xPages = Math.round(1 / xOffsetStep);
+            if (DEBUG) {
+                Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
+                Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+            }
+            EngineWindowPage current;
+            synchronized (mLock) {
+                if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
+                    mWindowPages = new EngineWindowPage[xPages];
+                    initWindowPages(mWindowPages, xOffsetStep);
+                }
+                if (mLocalColorsToAdd.size() != 0) {
+                    for (RectF colorArea : mLocalColorsToAdd) {
+                        if (!isValid(colorArea)) continue;
+                        mLocalColorAreas.add(colorArea);
+                        int colorPage = getRectFPage(colorArea, xOffsetStep);
+                        EngineWindowPage currentPage = mWindowPages[colorPage];
+                        if (currentPage == null) {
+                            currentPage = new EngineWindowPage();
+                            currentPage.addArea(colorArea);
+                            mWindowPages[colorPage] = currentPage;
+                        } else {
+                            currentPage.setLastUpdateTime(0);
+                            currentPage.removeColor(colorArea);
+                        }
+                    }
+                    mLocalColorsToAdd.clear();
+                }
+                if (xPage >= mWindowPages.length) {
+                    if (DEBUG) {
+                        Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
+                        Log.e(TAG, "error on page " + xPage + " out of " + xPages);
+                        Log.e(TAG,
+                                "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+                    }
+                    xPage = mWindowPages.length - 1;
+                }
+                current = mWindowPages[xPage];
+                if (current == null) {
+                    if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
+                    if (DEBUG) {
+                        Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+                    }
+                    current = new EngineWindowPage();
+                    mWindowPages[xPage] = current;
+                }
+            }
+            updatePage(current, xPage, xPages, xOffsetStep);
+        }
+
+        private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
+            float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
+
+            return (pageCenter > xOffset && pageCenter < mLastPageOffset)
+                || (pageCenter < xOffset && pageCenter > mLastPageOffset);
+        }
+
+        private void initWindowPages(EngineWindowPage[] windowPages, float step) {
+            for (int i = 0; i < windowPages.length; i++) {
+                windowPages[i] = new EngineWindowPage();
+            }
+            mLocalColorAreas.addAll(mLocalColorsToAdd);
+            mLocalColorsToAdd.clear();
+            for (RectF area: mLocalColorAreas) {
+                if (!isValid(area)) {
+                    mLocalColorAreas.remove(area);
+                    continue;
+                }
+                int pageNum = getRectFPage(area, step);
+                windowPages[pageNum].addArea(area);
+            }
+        }
+
+        void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
+                float xOffsetStep) {
+            // to save creating a runnable, check twice
+            long current = System.nanoTime() / 1_000_000;
+            AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
+            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+            currentPage.execSync((page) -> {
+                // just in case of race double check
+                long currentInner = System.nanoTime() / 1_000_000;
+                lapsed.set(currentInner - page.getLastUpdateTime());
+                page.setLastUpdateTime(currentInner);
+            });
+            long lastUpdate = currentPage.getLastUpdateTime();
+            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+            Surface surface = mSurfaceHolder.getSurface();
+            boolean widthIsLarger =
+                    mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
+            int smaller = widthIsLarger ? mSurfaceControl.getWidth()
+                    : mSurfaceControl.getHeight();
+            float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
+            int width = (int) (ratio * mSurfaceControl.getWidth());
+            int height = (int) (ratio * mSurfaceControl.getHeight());
+            if (width <= 0 || height <= 0) {
+                return;
+            }
+            Bitmap screenShot = Bitmap.createBitmap(width, height,
+                    Bitmap.Config.ARGB_8888);
+            final Bitmap finalScreenShot = screenShot;
+            Trace.beginSection("WallpaperService#pixelCopy");
+            PixelCopy.request(surface, screenShot, (res) -> {
+                Trace.endSection();
+                if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
+                if (res != PixelCopy.SUCCESS) {
+                    currentPage.execSync((p) -> {
+                        // reset the time
+                        p.setLastUpdateTime(lastUpdate);
+                        // assign the last bitmap taken for now
+                        p.setBitmap(mLastScreenshot);
+                    });
+                    return;
+                }
+                mLastScreenshot = finalScreenShot;
+                // going to hold this lock for a while
+                currentPage.execSync((p) -> {
+                    p.setBitmap(finalScreenShot);
+                });
+                updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+            }, mHandler);
+
+        }
+        // locked by the passed page
+        private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
+                float xOffsetStep) {
+            if (page.getBitmap() == null) return;
+            if (DEBUG) {
+                Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+                        + page.getAreas().size() + " and bitmap size of "
+                        + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
+            }
+            for (RectF area: page.getAreas()) {
+                RectF subArea = generateSubRect(area, pageIndx, numPages);
+                Bitmap b = page.getBitmap();
+                int x = Math.round(b.getWidth() * subArea.left);
+                int y = Math.round(b.getHeight() * subArea.top);
+                int width = Math.round(b.getWidth() * subArea.width());
+                int height = Math.round(b.getHeight() * subArea.height());
+                Bitmap target;
+                try {
+                    target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error creating page local color bitmap", e);
+                    continue;
+                }
+                WallpaperColors color = WallpaperColors.fromBitmap(target);
+                target.recycle();
+                WallpaperColors currentColor = page.getColors(area);
+
+                if (DEBUG) {
+                    Log.d(TAG, "getting local bitmap area x " + x + " y " + y
+                            + " width " + width + " height " + height + " for sub area " + subArea
+                            + " and with page " + pageIndx + " of " + numPages);
+
+                }
+                if (currentColor == null || !color.equals(currentColor)) {
+                    page.addWallpaperColors(area, color);
+                    if (DEBUG) {
+                        Log.d(TAG, "onLocalWallpaperColorsChanged"
+                                + " local color callback for area" + area + " for page " + pageIndx
+                                + " of " + numPages);
+                    }
+                    try {
+                        mConnection.onLocalWallpaperColorsChanged(area, color,
+                                mDisplayContext.getDisplayId());
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+                    }
+                }
+            }
+        }
+
+        private RectF generateSubRect(RectF in, int pageInx, int numPages) {
+            float minLeft = (float) (pageInx) / (float) (numPages);
+            float maxRight = (float) (pageInx + 1) / (float) (numPages);
+            float left = in.left;
+            float right = in.right;
+
+            // bound rect
+            if (left < minLeft) left = minLeft;
+            if (right > maxRight) right = maxRight;
+
+            // scale up the sub area then trim
+            left = (left * (float) numPages) % 1f;
+            right = (right * (float) numPages) % 1f;
+            if (right == 0f) {
+                right = 1f;
+            }
+
+            return new RectF(left, in.top, right, in.bottom);
+        }
+
+        private void resetWindowPages() {
+            if (supportsLocalColorExtraction()) return;
+            mLastWindowPage = -1;
+            synchronized (mLock) {
+                for (int i = 0; i < mWindowPages.length; i++) {
+                    EngineWindowPage page = mWindowPages[i];
+                    if (page != null) {
+                        page.execSync((p) -> {
+                            p.setLastUpdateTime(0L);
+                        });
+                    }
+                }
+            }
+        }
+
+        private int getRectFPage(RectF area, float step) {
+            if (!isValid(area)) return 0;
+            if (!validStep(step)) return 0;
+            int pages = Math.round(1 / step);
+            int page = Math.round(area.centerX() * pages);
+            if (page == pages) return pages - 1;
+            if (page == mWindowPages.length) page = mWindowPages.length - 1;
+            return page;
+        }
+
+        /**
+         * Add local colors areas of interest
+         * @param regions list of areas
+         * @hide
+         */
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            if (supportsLocalColorExtraction()) return;
+            if (DEBUG) {
+                Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
+            }
+            float step = mPendingXOffsetStep;
+
+            List<WallpaperColors> colors = getLocalWallpaperColors(regions);
+            synchronized (mLock) {
+                if (!validStep(step)) {
+                    mLocalColorsToAdd.addAll(regions);
+                    return;
+                }
+                for (int i = 0; i < regions.size(); i++) {
+                    RectF area = regions.get(i);
+                    if (!isValid(area)) continue;
+                    int pageInx = getRectFPage(area, step);
+                    // no page should be null
+                    EngineWindowPage page = mWindowPages[pageInx];
+
+                    if (page != null) {
+                        mLocalColorAreas.add(area);
+                        page.addArea(area);
+                        WallpaperColors color = colors.get(i);
+                        if (color != null && !color.equals(page.getColors(area))) {
+                            page.execSync(p -> {
+                                p.addWallpaperColors(area, color);
+                            });
+                        }
+                    } else {
+                        mLocalColorsToAdd.add(area);
+                    }
+                }
+            }
+
+            for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
+                try {
+                    mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
+                            mDisplayContext.getDisplayId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+                    return;
+                }
+            }
+        }
+
+        /**
+         * Remove local colors areas of interest if they exist
+         * @param regions list of areas
+         * @hide
+         */
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            if (supportsLocalColorExtraction()) return;
+            synchronized (mLock) {
+                float step = mPendingXOffsetStep;
+                mLocalColorsToAdd.removeAll(regions);
+                mLocalColorAreas.removeAll(regions);
+                if (!validStep(step)) {
+                    return;
+                }
+                for (int i = 0; i < regions.size(); i++) {
+                    RectF area = regions.get(i);
+                    if (!isValid(area)) continue;
+                    int pageInx = getRectFPage(area, step);
+                    // no page should be null
+                    EngineWindowPage page = mWindowPages[pageInx];
+                    if (page != null) {
+                        page.execSync(p -> {
+                            p.removeArea(area);
+                        });
+                    }
+                }
+            }
+        }
+
+        private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
+            ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
+            float step = mPendingXOffsetStep;
+            if (!validStep(step)) {
+                if (DEBUG) Log.d(TAG, "invalid step size " + step);
+                step = 1.0f;
+            }
+            for (int i = 0; i < areas.size(); i++) {
+                RectF currentArea = areas.get(i);
+                EngineWindowPage page;
+                RectF area;
+                int pageIndx;
+                synchronized (mLock) {
+                    pageIndx = getRectFPage(currentArea, step);
+                    if (mWindowPages.length == 0 || pageIndx < 0
+                            || pageIndx > mWindowPages.length || !isValid(currentArea)) {
+                        colors.add(null);
+                        continue;
+                    }
+                    area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
+                    page = mWindowPages[pageIndx];
+                }
+                if (page == null) {
+                    colors.add(null);
+                    continue;
+                }
+                float finalStep = step;
+                int finalPageIndx = pageIndx;
+                Bitmap screenShot = page.getBitmap();
+                if (screenShot == null || screenShot.isRecycled()) {
+                    if (DEBUG) {
+                        Log.d(TAG, "invalid bitmap " + screenShot
+                                + " for page " + finalPageIndx);
+                    }
+                    page.setLastUpdateTime(0);
+                    colors.add(null);
+                    continue;
+                }
+                Bitmap b = screenShot;
+                Rect subImage = new Rect(
+                        Math.round(area.left * b.getWidth() / finalStep),
+                        Math.round(area.top * b.getHeight()),
+                        Math.round(area.right * b.getWidth() / finalStep),
+                        Math.round(area.bottom * b.getHeight())
+                );
+                subImage = fixRect(b, subImage);
+                if (DEBUG) {
+                    Log.d(TAG, "getting subbitmap of " + subImage.toString()
+                            + " for RectF " + area.toString()
+                            + " screenshot width " + screenShot.getWidth() + " height "
+                            + screenShot.getHeight());
+                }
+                Bitmap colorImg = Bitmap.createBitmap(screenShot,
+                        subImage.left, subImage.top, subImage.width(), subImage.height());
+                if (DEBUG) {
+                    Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
+                            + colorImg.getHeight());
+                }
+                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+                colors.add(color);
+            }
+            return colors;
+        }
+
+        // fix the rect to be included within the bounds of the bitmap
+        private Rect fixRect(Bitmap b, Rect r) {
+            r.left =  r.left >= r.right || r.left >= b.getWidth() || r.left > 0
+                    ? 0
+                    : r.left;
+            r.right =  r.left >= r.right || r.right > b.getWidth()
+                    ? b.getWidth()
+                    : r.right;
+            return r;
+        }
+
+        private boolean validStep(float step) {
+            return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
         }
 
         void doCommand(WallpaperCommand cmd) {
@@ -1371,6 +1834,16 @@
         };
     }
 
+    private boolean isValid(RectF area) {
+        boolean valid = area.bottom > area.top && area.left < area.right
+                && LOCAL_COLOR_BOUNDS.contains(area);
+        return valid;
+    }
+
+    private boolean inRectFRange(float number) {
+        return number >= 0f && number <= 1f;
+    }
+
     class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
             implements HandlerCaller.Callback {
         private final HandlerCaller mCaller;
@@ -1477,6 +1950,14 @@
             mCaller.sendMessage(msg);
         }
 
+        public void addLocalColorsAreas(List<RectF> regions) {
+            mEngine.addLocalColorsAreas(regions);
+        }
+
+        public void removeLocalColorsAreas(List<RectF> regions) {
+            mEngine.removeLocalColorsAreas(regions);
+        }
+
         public void destroy() {
             Message msg = mCaller.obtainMessage(DO_DETACH);
             mCaller.sendMessage(msg);
@@ -1516,14 +1997,15 @@
             }
             switch (message.what) {
                 case DO_ATTACH: {
+                    Engine engine = onCreateEngine();
+                    mEngine = engine;
                     try {
                         mConnection.attachEngine(this, mDisplayId);
                     } catch (RemoteException e) {
+                        engine.detach();
                         Log.w(TAG, "Wallpaper host disappeared", e);
                         return;
                     }
-                    Engine engine = onCreateEngine();
-                    mEngine = engine;
                     mActiveEngines.add(engine);
                     engine.attach(this);
                     return;
diff --git a/core/java/android/speech/tts/ITextToSpeechManager.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
new file mode 100644
index 0000000..e6b63df
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.speech.tts;
+
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+/**
+ * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
+ * specified provider proxied by the system service.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechManager {
+    void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
+}
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/core/java/android/speech/tts/ITextToSpeechSession.aidl
new file mode 100644
index 0000000..b2afeb0
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSession.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.speech.tts;
+
+/**
+ * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ *
+ * @see ITextToSpeechManager
+ * @see ITextToSpeechSessionCallback
+ *
+ * {@hide}
+ */
+oneway interface ITextToSpeechSession {
+
+    /**
+     * Disconnects the client from the TTS provider.
+     */
+    void disconnect();
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
new file mode 100644
index 0000000..545622a
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+import android.speech.tts.ITextToSpeechSession;
+
+/**
+ * Callback interface for a session created by {@link ITextToSpeechManager} API.
+ *
+ * @hide
+ */
+oneway interface ITextToSpeechSessionCallback {
+
+    void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
+
+    void onDisconnected();
+
+    void onError(in String errorInfo);
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a18538..5d66dc7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,6 +35,7 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -51,6 +52,7 @@
 import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  *
@@ -695,6 +697,8 @@
         public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
     }
 
+    private static final boolean DEBUG = false;
+
     private final Context mContext;
     @UnsupportedAppUsage
     private Connection mConnectingServiceConnection;
@@ -716,6 +720,9 @@
     private final Map<CharSequence, Uri> mUtterances;
     private final Bundle mParams = new Bundle();
     private final TtsEngines mEnginesHelper;
+    private final boolean mIsSystem;
+    @Nullable private final Executor mInitExecutor;
+
     @UnsupportedAppUsage
     private volatile String mCurrentEngine = null;
 
@@ -758,8 +765,21 @@
      */
     public TextToSpeech(Context context, OnInitListener listener, String engine,
             String packageName, boolean useFallback) {
+        this(context, /* initExecutor= */ null, listener, engine, packageName,
+                useFallback, /* isSystem= */ true);
+    }
+
+    /**
+     * Used internally to instantiate TextToSpeech objects.
+     *
+     * @hide
+     */
+    private TextToSpeech(Context context, @Nullable Executor initExecutor,
+            OnInitListener initListener, String engine, String packageName, boolean useFallback,
+            boolean isSystem) {
         mContext = context;
-        mInitListener = listener;
+        mInitExecutor = initExecutor;
+        mInitListener = initListener;
         mRequestedEngine = engine;
         mUseFallback = useFallback;
 
@@ -768,6 +788,9 @@
         mUtteranceProgressListener = null;
 
         mEnginesHelper = new TtsEngines(mContext);
+
+        mIsSystem = isSystem;
+
         initTts();
     }
 
@@ -842,10 +865,14 @@
     }
 
     private boolean connectToEngine(String engine) {
-        Connection connection = new Connection();
-        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
-        intent.setPackage(engine);
-        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        Connection connection;
+        if (mIsSystem) {
+            connection = new SystemConnection();
+        } else {
+            connection = new DirectConnection();
+        }
+
+        boolean bound = connection.connect(engine);
         if (!bound) {
             Log.e(TAG, "Failed to bind to " + engine);
             return false;
@@ -857,11 +884,19 @@
     }
 
     private void dispatchOnInit(int result) {
-        synchronized (mStartLock) {
-            if (mInitListener != null) {
-                mInitListener.onInit(result);
-                mInitListener = null;
+        Runnable onInitCommand = () -> {
+            synchronized (mStartLock) {
+                if (mInitListener != null) {
+                    mInitListener.onInit(result);
+                    mInitListener = null;
+                }
             }
+        };
+
+        if (mInitExecutor != null) {
+            mInitExecutor.execute(onInitCommand);
+        } else {
+            onInitCommand.run();
         }
     }
 
@@ -2127,13 +2162,17 @@
         return mEnginesHelper.getEngines();
     }
 
-    private class Connection implements ServiceConnection {
+    private abstract class Connection implements ServiceConnection {
         private ITextToSpeechService mService;
 
         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
 
         private boolean mEstablished;
 
+        abstract boolean connect(String engine);
+
+        abstract void disconnect();
+
         private final ITextToSpeechCallback.Stub mCallback =
                 new ITextToSpeechCallback.Stub() {
                     public void onStop(String utteranceId, boolean isStarted)
@@ -2199,11 +2238,6 @@
                 };
 
         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
-            private final ComponentName mName;
-
-            public SetupConnectionAsyncTask(ComponentName name) {
-                mName = name;
-            }
 
             @Override
             protected Integer doInBackground(Void... params) {
@@ -2227,7 +2261,7 @@
                             mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
                         }
 
-                        Log.i(TAG, "Set up connection to " + mName);
+                        Log.i(TAG, "Setting up the connection to TTS engine...");
                         return SUCCESS;
                     } catch (RemoteException re) {
                         Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2249,11 +2283,11 @@
         }
 
         @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
             synchronized(mStartLock) {
                 mConnectingServiceConnection = null;
 
-                Log.i(TAG, "Connected to " + name);
+                Log.i(TAG, "Connected to TTS engine");
 
                 if (mOnSetupConnectionAsyncTask != null) {
                     mOnSetupConnectionAsyncTask.cancel(false);
@@ -2263,7 +2297,7 @@
                 mServiceConnection = Connection.this;
 
                 mEstablished = false;
-                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
                 mOnSetupConnectionAsyncTask.execute();
             }
         }
@@ -2277,7 +2311,7 @@
          *
          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
          */
-        private boolean clearServiceConnection() {
+        protected boolean clearServiceConnection() {
             synchronized(mStartLock) {
                 boolean result = false;
                 if (mOnSetupConnectionAsyncTask != null) {
@@ -2295,8 +2329,8 @@
         }
 
         @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.i(TAG, "Asked to disconnect from " + name);
+        public void onServiceDisconnected(ComponentName componentName) {
+            Log.i(TAG, "Disconnected from TTS engine");
             if (clearServiceConnection()) {
                 /* We need to protect against a rare case where engine
                  * dies just after successful connection - and we process onServiceDisconnected
@@ -2308,11 +2342,6 @@
             }
         }
 
-        public void disconnect() {
-            mContext.unbindService(this);
-            clearServiceConnection();
-        }
-
         public boolean isEstablished() {
             return mService != null && mEstablished;
         }
@@ -2342,6 +2371,91 @@
         }
     }
 
+    // Currently all the clients are routed through the System connection. Direct connection
+    // is left for debugging, testing and benchmarking purposes.
+    // TODO(b/179599071): Remove direct connection once system one is fully tested.
+    private class DirectConnection extends Connection {
+        @Override
+        boolean connect(String engine) {
+            Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+            intent.setPackage(engine);
+            return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+        }
+
+        @Override
+        void disconnect() {
+            mContext.unbindService(this);
+            clearServiceConnection();
+        }
+    }
+
+    private class SystemConnection extends Connection {
+
+        @Nullable
+        private volatile ITextToSpeechSession mSession;
+
+        @Override
+        boolean connect(String engine) {
+            IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
+
+            ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
+
+            if (manager == null) {
+                Log.e(TAG, "System service is not available!");
+                return false;
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Connecting to engine: " + engine);
+            }
+
+            try {
+                manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
+                    @Override
+                    public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
+                        mSession = session;
+                        onServiceConnected(
+                                /* componentName= */ null,
+                                serviceBinder);
+                    }
+
+                    @Override
+                    public void onDisconnected() {
+                        onServiceDisconnected(/* componentName= */ null);
+                    }
+
+                    @Override
+                    public void onError(String errorInfo) {
+                        Log.w(TAG, "System TTS connection error: " + errorInfo);
+                        // The connection was not established successfully - handle as
+                        // disconnection: clear the state and notify the user.
+                        onServiceDisconnected(/* componentName= */ null);
+                    }
+                });
+
+                return true;
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Error communicating with the System Server: ", ex);
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        void disconnect() {
+            ITextToSpeechSession session = mSession;
+
+            if (session != null) {
+                try {
+                    session.disconnect();
+                } catch (RemoteException ex) {
+                    Log.w(TAG, "Error disconnecting session", ex);
+                }
+
+                clearServiceConnection();
+            }
+        }
+    }
+
     private interface Action<R> {
         R run(ITextToSpeechService service) throws RemoteException;
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1273b49..ba78f96 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.
          *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 228ee3c..f1f6786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1375,6 +1375,7 @@
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
                 mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                         attrs.getTitle().toString());
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
                 updateColorModeIfNeeded(attrs.getColorMode());
                 updateForceDarkMode();
                 if (mAttachInfo.mThreadedRenderer != null) {
@@ -1941,6 +1942,10 @@
             mBlastBufferQueue.destroy();
             mBlastBufferQueue = null;
         }
+
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+        }
     }
 
     /**
@@ -7651,6 +7656,9 @@
                     mSurface.transferFrom(blastSurface);
                 }
             }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+            }
         } else {
             destroySurface();
         }
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/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/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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6cb4b81..4e3d99b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -48,6 +48,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Outline;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -99,6 +100,8 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
@@ -171,6 +174,11 @@
      */
     private static final int MAX_NESTED_VIEWS = 10;
 
+    /**
+     * Maximum number of RemoteViews that can be specified in constructor.
+     */
+    private static final int MAX_INIT_VIEW_COUNT = 16;
+
     // The unique identifiers for each custom {@link Action}.
     private static final int SET_ON_CLICK_RESPONSE_TAG = 1;
     private static final int REFLECTION_ACTION_TAG = 2;
@@ -290,7 +298,7 @@
      * The resource ID of the layout file. (Added to the parcel)
      */
     @UnsupportedAppUsage
-    private final int mLayoutId;
+    private int mLayoutId;
 
     /**
      * The resource ID of the layout file in dark text mode. (Added to the parcel)
@@ -322,6 +330,7 @@
      */
     private static final int MODE_NORMAL = 0;
     private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
+    private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2;
 
     /**
      * Used in conjunction with the special constructor
@@ -331,12 +340,26 @@
     private RemoteViews mLandscape = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private RemoteViews mPortrait = null;
+    /**
+     * List of RemoteViews with their ideal size. There must be at least two if the map is not null.
+     *
+     * The smallest remote view is always the last element in the list.
+     */
+    private List<RemoteViews> mSizedRemoteViews = null;
+
+    /**
+     * Ideal size for this RemoteViews.
+     *
+     * Only to be used on children views used in a {@link RemoteViews} with
+     * {@link RemoteViews#hasSizedRemoteViews()}.
+     */
+    private PointF mIdealSize = null;
 
     @ApplyFlags
     private int mApplyFlags = 0;
 
     /** Class cookies of the Parcel this instance was read from. */
-    private final Map<Class, Object> mClassCookies;
+    private Map<Class, Object> mClassCookies;
 
     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response)
             -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -2768,23 +2791,50 @@
         mClassCookies = null;
     }
 
+    private boolean hasMultipleLayouts() {
+        return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews();
+    }
+
     private boolean hasLandscapeAndPortraitLayouts() {
         return (mLandscape != null) && (mPortrait != null);
     }
 
+    private boolean hasSizedRemoteViews() {
+        return mSizedRemoteViews != null;
+    }
+
+    private @Nullable PointF getIdealSize() {
+        return mIdealSize;
+    }
+
+    private void setIdealSize(@Nullable PointF size) {
+        mIdealSize = size;
+    }
+
+    /**
+     * Finds the smallest view in {@code mSizedRemoteViews}.
+     * This method must not be called if {@code mSizedRemoteViews} is null.
+     */
+    private RemoteViews findSmallestRemoteView() {
+        return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1);
+    }
+
     /**
      * Create a new RemoteViews object that will inflate as the specified
      * landspace or portrait RemoteViews, depending on the current configuration.
      *
      * @param landscape The RemoteViews to inflate in landscape configuration
      * @param portrait The RemoteViews to inflate in portrait configuration
+     * @throws IllegalArgumentException if either landscape or portrait are null or if they are
+     *   not from the same application
      */
     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
         if (landscape == null || portrait == null) {
-            throw new RuntimeException("Both RemoteViews must be non-null");
+            throw new IllegalArgumentException("Both RemoteViews must be non-null");
         }
         if (!landscape.hasSameAppInfo(portrait.mApplication)) {
-            throw new RuntimeException("Both RemoteViews must share the same package and user");
+            throw new IllegalArgumentException(
+                    "Both RemoteViews must share the same package and user");
         }
         mApplication = portrait.mApplication;
         mLayoutId = portrait.mLayoutId;
@@ -2802,9 +2852,84 @@
     }
 
     /**
+     * Create a new RemoteViews object that will inflate the layout with the closest size
+     * specification.
+     *
+     * The default remote views in that case is always the smallest one provided.
+     *
+     * @param remoteViews Mapping of size to layout.
+     * @throws IllegalArgumentException if the map is empty, there are more than
+     *   MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application.
+     */
+    public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) {
+        if (remoteViews.isEmpty()) {
+            throw new IllegalArgumentException("The set of RemoteViews cannot be empty");
+        }
+        if (remoteViews.size() > MAX_INIT_VIEW_COUNT) {
+            throw new IllegalArgumentException("Too many RemoteViews in constructor");
+        }
+        if (remoteViews.size() == 1) {
+            initializeFrom(remoteViews.values().iterator().next());
+            return;
+        }
+        mBitmapCache = new BitmapCache();
+        mClassCookies = initializeSizedRemoteViews(
+                remoteViews.entrySet().stream().map(
+                        entry -> {
+                            entry.getValue().setIdealSize(entry.getKey());
+                            return entry.getValue();
+                        }
+                ).iterator()
+        );
+
+        RemoteViews smallestView = findSmallestRemoteView();
+        mApplication = smallestView.mApplication;
+        mLayoutId = smallestView.mLayoutId;
+        mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
+    }
+
+    // Initialize mSizedRemoteViews and return the class cookies.
+    private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) {
+        List<RemoteViews> sizedRemoteViews = new ArrayList<>();
+        Map<Class, Object> classCookies = null;
+        float viewArea = Float.MAX_VALUE;
+        RemoteViews smallestView = null;
+        while (remoteViews.hasNext()) {
+            RemoteViews view = remoteViews.next();
+            PointF size = view.getIdealSize();
+            float newViewArea = size.x * size.y;
+            if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) {
+                throw new IllegalArgumentException(
+                        "All RemoteViews must share the same package and user");
+            }
+            if (smallestView == null || newViewArea < viewArea) {
+                if (smallestView != null) {
+                    sizedRemoteViews.add(smallestView);
+                }
+                viewArea = newViewArea;
+                smallestView = view;
+            } else {
+                sizedRemoteViews.add(view);
+            }
+            configureRemoteViewsAsChild(view);
+            view.setIdealSize(size);
+            if (classCookies == null) {
+                classCookies = view.mClassCookies;
+            }
+        }
+        sizedRemoteViews.add(smallestView);
+        mSizedRemoteViews = sizedRemoteViews;
+        return classCookies;
+    }
+
+    /**
      * Creates a copy of another RemoteViews.
      */
     public RemoteViews(RemoteViews src) {
+        initializeFrom(src);
+    }
+
+    private void initializeFrom(RemoteViews src) {
         mBitmapCache = src.mBitmapCache;
         mApplication = src.mApplication;
         mIsRoot = src.mIsRoot;
@@ -2812,12 +2937,20 @@
         mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
         mApplyFlags = src.mApplyFlags;
         mClassCookies = src.mClassCookies;
+        mIdealSize = src.mIdealSize;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = new RemoteViews(src.mLandscape);
             mPortrait = new RemoteViews(src.mPortrait);
         }
 
+        if (src.hasSizedRemoteViews()) {
+            mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
+            for (RemoteViews srcView : src.mSizedRemoteViews) {
+                mSizedRemoteViews.add(new RemoteViews(srcView));
+            }
+        }
+
         if (src.mActions != null) {
             Parcel p = Parcel.obtain();
             p.putClassCookies(mClassCookies);
@@ -2867,10 +3000,29 @@
         if (mode == MODE_NORMAL) {
             mApplication = parcel.readInt() == 0 ? info :
                     ApplicationInfo.CREATOR.createFromParcel(parcel);
+            mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel);
             mLayoutId = parcel.readInt();
             mLightBackgroundLayoutId = parcel.readInt();
 
             readActionsFromParcel(parcel, depth);
+        } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) {
+            int numViews = parcel.readInt();
+            if (numViews > MAX_INIT_VIEW_COUNT) {
+                throw new IllegalArgumentException(
+                        "Too many views in mapping from size to RemoteViews.");
+            }
+            List<RemoteViews> remoteViews = new ArrayList<>(numViews);
+            for (int i = 0; i < numViews; i++) {
+                RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
+                        mClassCookies);
+                info = view.mApplication;
+                remoteViews.add(view);
+            }
+            initializeSizedRemoteViews(remoteViews.iterator());
+            RemoteViews smallestView = findSmallestRemoteView();
+            mApplication = smallestView.mApplication;
+            mLayoutId = smallestView.mLayoutId;
+            mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
         } else {
             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
             mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
@@ -2990,16 +3142,20 @@
      */
     private void setBitmapCache(BitmapCache bitmapCache) {
         mBitmapCache = bitmapCache;
-        if (!hasLandscapeAndPortraitLayouts()) {
+        if (hasSizedRemoteViews()) {
+            for (RemoteViews remoteView : mSizedRemoteViews) {
+                remoteView.setBitmapCache(bitmapCache);
+            }
+        } else if (hasLandscapeAndPortraitLayouts()) {
+            mLandscape.setBitmapCache(bitmapCache);
+            mPortrait.setBitmapCache(bitmapCache);
+        } else {
             if (mActions != null) {
                 final int count = mActions.size();
-                for (int i= 0; i < count; ++i) {
+                for (int i = 0; i < count; ++i) {
                     mActions.get(i).setBitmapCache(bitmapCache);
                 }
             }
-        } else {
-            mLandscape.setBitmapCache(bitmapCache);
-            mPortrait.setBitmapCache(bitmapCache);
         }
     }
 
@@ -3018,10 +3174,10 @@
      * @param a The action to add
      */
     private void addAction(Action a) {
-        if (hasLandscapeAndPortraitLayouts()) {
-            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
-                    " layouts cannot be modified. Instead, fully configure the landscape and" +
-                    " portrait layouts individually before constructing the combined layout.");
+        if (hasMultipleLayouts()) {
+            throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
+                    + " or size cannot be modified. Instead, fully configure each layouts"
+                    + " individually before constructing the combined layout.");
         }
         if (mActions == null) {
             mActions = new ArrayList<>();
@@ -4100,14 +4256,79 @@
             int orientation = context.getResources().getConfiguration().orientation;
             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                 return mLandscape;
-            } else {
-                return mPortrait;
             }
+            return mPortrait;
+        }
+        if (hasSizedRemoteViews()) {
+            return findSmallestRemoteView();
         }
         return this;
     }
 
     /**
+     * Returns the square distance between two points.
+     *
+     * This is particularly useful when we only care about the ordering of the distances.
+     */
+    private static float squareDistance(PointF p1, PointF p2) {
+        float dx = p1.x - p2.x;
+        float dy = p1.y - p2.y;
+        return dx * dx + dy * dy;
+    }
+
+    /**
+     * Returns whether the layout fits in the space available to the widget.
+     *
+     * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions
+     * are smaller than the ones of the widget, adding some padding to account for rounding errors.
+     */
+    private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) {
+        return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x)
+                && (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y);
+    }
+
+    /**
+     * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the
+     * size of the widget.
+     *
+     * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is
+     * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the
+     * diagonal the most similar to the widget. If no layout fits or the size of the widget is
+     * not specified, the one with the smallest area will be chosen.
+     */
+    private RemoteViews getRemoteViewsToApply(@NonNull Context context,
+            @Nullable PointF widgetSize) {
+        if (!hasSizedRemoteViews()) {
+            // If there isn't multiple remote views, fall back on the previous methods.
+            return getRemoteViewsToApply(context);
+        }
+        // Find the better remote view
+        RemoteViews bestFit = null;
+        float bestSqDist = Float.MAX_VALUE;
+        for (RemoteViews layout : mSizedRemoteViews) {
+            PointF layoutSize = layout.getIdealSize();
+            if (fitsIn(layoutSize, widgetSize)) {
+                if (bestFit == null) {
+                    bestFit = layout;
+                    bestSqDist = squareDistance(layoutSize, widgetSize);
+                } else {
+                    float newSqDist = squareDistance(layoutSize, widgetSize);
+                    if (newSqDist < bestSqDist) {
+                        bestFit = layout;
+                        bestSqDist = newSqDist;
+                    }
+                }
+            }
+        }
+        if (bestFit == null) {
+            Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize);
+            return findSmallestRemoteView();
+        }
+        return bestFit;
+    }
+
+
+    /**
      * Inflates the view hierarchy represented by this object and applies
      * all of the actions.
      *
@@ -4124,7 +4345,13 @@
 
     /** @hide */
     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return apply(context, parent, handler, null);
+    }
+
+    /** @hide */
+    public View apply(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler, @Nullable PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         View result = inflateView(context, rvToApply, parent);
         rvToApply.performApply(result, parent, handler);
@@ -4132,9 +4359,17 @@
     }
 
     /** @hide */
-    public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler,
+    public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler,
             @StyleRes int applyThemeResId) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return applyWithTheme(context, parent, handler, applyThemeResId, null);
+    }
+
+    /** @hide */
+    public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler,
+            @StyleRes int applyThemeResId, @Nullable PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         View result = inflateView(context, rvToApply, parent, applyThemeResId);
         rvToApply.performApply(result, parent, handler);
@@ -4219,12 +4454,26 @@
     /** @hide */
     public CancellationSignal applyAsync(Context context, ViewGroup parent,
             Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
-        return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
+        return applyAsync(context, parent, executor, listener, handler, null);
+    }
+
+    /** @hide */
+    public CancellationSignal applyAsync(Context context, ViewGroup parent,
+            Executor executor, OnViewAppliedListener listener, OnClickHandler handler,
+            PointF size) {
+        return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor(
+                executor);
     }
 
     private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
             OnViewAppliedListener listener, OnClickHandler handler) {
-        return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
+        return getAsyncApplyTask(context, parent, listener, handler, null);
+    }
+
+    private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
+            OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+        return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context,
+                listener,
                 handler, null);
     }
 
@@ -4341,12 +4590,18 @@
 
     /** @hide */
     public void reapply(Context context, View v, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        reapply(context, v, handler, null);
+    }
 
-        // In the case that a view has this RemoteViews applied in one orientation, is persisted
-        // across orientation change, and has the RemoteViews re-applied in the new orientation,
-        // we throw an exception, since the layouts may be completely unrelated.
-        if (hasLandscapeAndPortraitLayouts()) {
+    /** @hide */
+    public void reapply(Context context, View v, OnClickHandler handler, PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+        // In the case that a view has this RemoteViews applied in one orientation or size, is
+        // persisted across change, and has the RemoteViews re-applied in a different situation
+        // (orientation or size), we throw an exception, since the layouts may be completely
+        // unrelated.
+        if (hasMultipleLayouts()) {
             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
@@ -4377,12 +4632,18 @@
     /** @hide */
     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
             OnViewAppliedListener listener, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return reapplyAsync(context, v, executor, listener, handler, null);
+    }
+
+    /** @hide */
+    public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
+            OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         // In the case that a view has this RemoteViews applied in one orientation, is persisted
         // across orientation change, and has the RemoteViews re-applied in the new orientation,
         // we throw an exception, since the layouts may be completely unrelated.
-        if (hasLandscapeAndPortraitLayouts()) {
+        if (hasMultipleLayouts()) {
             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
@@ -4466,7 +4727,7 @@
     }
 
     public void writeToParcel(Parcel dest, int flags) {
-        if (!hasLandscapeAndPortraitLayouts()) {
+        if (!hasMultipleLayouts()) {
             dest.writeInt(MODE_NORMAL);
             // We only write the bitmap cache if we are the root RemoteViews, as this cache
             // is shared by all children.
@@ -4479,9 +4740,26 @@
                 dest.writeInt(1);
                 mApplication.writeToParcel(dest, flags);
             }
+            if (mIsRoot || mIdealSize == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                mIdealSize.writeToParcel(dest, flags);
+            }
             dest.writeInt(mLayoutId);
             dest.writeInt(mLightBackgroundLayoutId);
             writeActionsToParcel(dest);
+        } else if (hasSizedRemoteViews()) {
+            dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
+            if (mIsRoot) {
+                mBitmapCache.writeBitmapsToParcel(dest, flags);
+            }
+            int childFlags = flags;
+            dest.writeInt(mSizedRemoteViews.size());
+            for (RemoteViews view : mSizedRemoteViews) {
+                view.writeToParcel(dest, childFlags);
+                childFlags |= PARCELABLE_ELIDE_DUPLICATES;
+            }
         } else {
             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
             // We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -4735,11 +5013,9 @@
          * before starting the intent.
          *
          * @param fillIntent The intent which will be combined with the parent's PendingIntent in
-         *                  order to determine the behavior of the response
-         *
+         *                   order to determine the behavior of the response
          * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent)
          * @see RemoteViews#setOnClickFillInIntent(int, Intent)
-         * @return
          */
         @NonNull
         public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
@@ -4754,9 +5030,8 @@
          * the epicenter for the exit Transition. The position of the associated shared element in
          * the launched Activity will be the epicenter of its entering Transition.
          *
-         * @param viewId The id of the view to be shared as part of the transition
+         * @param viewId            The id of the view to be shared as part of the transition
          * @param sharedElementName The shared element name for this view
-         *
          * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
          */
         @NonNull
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index f3de982..64d09de 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -98,7 +98,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600)
-    private EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowTop;
 
     /**
      * Tracks the state of the bottom edge glow.
@@ -108,7 +108,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386)
-    private EdgeEffect mEdgeGlowBottom = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowBottom;
 
     /**
      * Position of the last motion event.
@@ -213,6 +213,8 @@
 
     public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mEdgeGlowTop = new EdgeEffect(context, attrs);
+        mEdgeGlowBottom = new EdgeEffect(context, attrs);
         initScrollView();
 
         final TypedArray a = context.obtainStyledAttributes(
@@ -679,7 +681,15 @@
                  * isFinished() is correct.
                 */
                 mScroller.computeScrollOffset();
-                mIsBeingDragged = !mScroller.isFinished();
+                mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
+                    || !mEdgeGlowTop.isFinished();
+                // Catch the edge effect if it is active.
+                if (!mEdgeGlowTop.isFinished()) {
+                    mEdgeGlowTop.onPullDistance(0f, ev.getX() / getWidth());
+                }
+                if (!mEdgeGlowBottom.isFinished()) {
+                    mEdgeGlowBottom.onPullDistance(0f, 1f - ev.getX() / getWidth());
+                }
                 if (mIsBeingDragged && mScrollStrictSpan == null) {
                     mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                 }
@@ -732,7 +742,8 @@
                 if (getChildCount() == 0) {
                     return false;
                 }
-                if ((mIsBeingDragged = !mScroller.isFinished())) {
+                if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowTop.isFinished()
+                        || !mEdgeGlowBottom.isFinished())) {
                     final ViewParent parent = getParent();
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
@@ -793,6 +804,21 @@
                     boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
 
+                    final float displacement = ev.getX(activePointerIndex) / getWidth();
+                    if (canOverscroll) {
+                        int consumed = 0;
+                        if (deltaY < 0 && mEdgeGlowBottom.getDistance() != 0f) {
+                            consumed = Math.round(getHeight()
+                                    * mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+                                    1 - displacement));
+                        } else if (deltaY > 0 && mEdgeGlowTop.getDistance() != 0f) {
+                            consumed = Math.round(-getHeight()
+                                    * mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+                                    displacement));
+                        }
+                        deltaY -= consumed;
+                    }
+
                     // Calling overScrollBy will call onOverScrolled, which
                     // calls onScrollChanged if applicable.
                     if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
@@ -807,17 +833,17 @@
                         mLastMotionY -= mScrollOffset[1];
                         vtev.offsetLocation(0, mScrollOffset[1]);
                         mNestedYOffset += mScrollOffset[1];
-                    } else if (canOverscroll) {
+                    } else if (canOverscroll && deltaY != 0f) {
                         final int pulledToY = oldY + deltaY;
                         if (pulledToY < 0) {
-                            mEdgeGlowTop.onPull((float) deltaY / getHeight(),
-                                    ev.getX(activePointerIndex) / getWidth());
+                            mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+                                    displacement);
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
                         } else if (pulledToY > range) {
-                            mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
-                                    1.f - ev.getX(activePointerIndex) / getWidth());
+                            mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+                                    1.f - displacement);
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
diff --git a/core/java/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/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/jni/Android.bp b/core/jni/Android.bp
index 9510f27..6983d35c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -184,6 +184,7 @@
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
+                "com_android_internal_os_KernelCpuBpfTracking.cpp",
                 "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
                 "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
                 "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba43ee7..6456e4e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1403,6 +1403,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 -->
@@ -4372,6 +4381,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 +5492,9 @@
     <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
+    <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
+        android:protectionLevel="signature|recents" />
 
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
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/values/attrs.xml b/core/res/res/values/attrs.xml
index 927a8b4..69bb20c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6389,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">
@@ -8003,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. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5546621..e7c4947 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -245,19 +245,19 @@
     <color name="system_main_0">#ffffff</color>
     <!-- Shade of the main system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_50">#f0f0f0</color>
+    <color name="system_main_50">#f2f2f2</color>
     <!-- Shade of the main system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_100">#e2e2e2</color>
+    <color name="system_main_100">#e3e3e3</color>
     <!-- Shade of the main system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_200">#c6c6c6</color>
+    <color name="system_main_200">#c7c7c7</color>
     <!-- Shade of the main 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.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_400">#909090</color>
+    <color name="system_main_400">#8f8f8f</color>
     <!-- Shade of the main system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
     <color name="system_main_500">#757575</color>
@@ -266,13 +266,13 @@
     <color name="system_main_600">#5e5e5e</color>
     <!-- Shade of the main system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_700">#464646</color>
+    <color name="system_main_700">#474747</color>
     <!-- Shade of the main 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.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_900">#1b1b1b</color>
+    <color name="system_main_900">#1f1f1f</color>
     <!-- Darkest shade of the main color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
     <color name="system_main_1000">#000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b7c755e..dd048f3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,6 +1946,8 @@
     <string name="config_systemAutomotiveProjection" translatable="false"></string>
     <!-- The name of the package that will hold the system cluster service role. -->
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
+    <!-- The name of the package that will hold the system shell role. -->
+    <string name="config_systemShell" translatable="false">com.android.shell</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
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 278708a..1ebcf77 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3071,6 +3071,11 @@
     <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">
@@ -3111,6 +3116,11 @@
 
   <public-group type="dimen" first-id="0x01050008">
     <!-- dimension definitions go here -->
+
+    <!-- System-provided dimensions for app widgets. -->
+    <public name="system_app_widget_background_radius" />
+    <public name="system_app_widget_inner_radius" />
+    <public name="system_app_widget_internal_padding" />
   </public-group>
 
   <public-group type="bool" first-id="0x01110007">
@@ -3126,6 +3136,8 @@
     <public name="config_systemAutomotiveCluster" />
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveProjection" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemShell" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 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/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/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
index be382600..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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 403dc65..fe4d0cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -316,6 +316,8 @@
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.ACCESS_LOWPAN_STATE"/>
         <permission name="android.permission.BACKUP"/>
+        <!-- Needed for test only -->
+        <permission name="android.permission.BATTERY_PREDICTION"/>
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ded4a27..84da930 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1075,12 +1075,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",
@@ -2821,12 +2815,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 +3319,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/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index b70fa0e..88cf96a 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -39,6 +39,7 @@
 import android.view.NativeVectorDrawableAnimator;
 import android.view.PixelCopy;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.animation.AnimationUtils;
 
@@ -314,6 +315,16 @@
     }
 
     /**
+     * Sets the SurfaceControl to be used internally inside render thread
+     * @hide
+     * @param surfaceControl The surface control to pass to render thread in hwui.
+     *        If null, any previous references held in render thread will be discarded.
+    */
+    public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) {
+        nSetSurfaceControl(mNativeProxy, surfaceControl != null ? surfaceControl.mNativeObject : 0);
+    }
+
+    /**
      * Sets the parameters that can be used to control a render request for a
      * {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
      * than a single frame request.
@@ -1216,6 +1227,8 @@
 
     private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
 
+    private static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl);
+
     private static native boolean nPause(long nativeProxy);
 
     private static native void nSetStopped(long nativeProxy, boolean stopped);
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..2b0d7e5 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -236,6 +236,47 @@
  * keyStore.load(null);
  * key = (SecretKey) keyStore.getKey("key2", null);
  * }</pre>
+ *
+ * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3>
+ * This example illustrates how to generate an elliptic curve key pair, used to establish a shared
+ * secret with another party using ECDH key agreement.
+ * <pre> {@code
+ * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+ *         KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ * keyPairGenerator.initialize(
+ *         new KeyGenParameterSpec.Builder(
+ *             "eckeypair",
+ *             KeyProperties.PURPOSE_AGREE_KEY)
+ *             .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+ *             .build());
+ * KeyPair myKeyPair = keyPairGenerator.generateKeyPair();
+ *
+ * // Exchange public keys with server. A new ephemeral key MUST be used for every message.
+ * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server.
+ *
+ * // Create a shared secret based on our private key and the other party's public key.
+ * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore");
+ * keyAgreement.init(myKeyPair.getPrivate());
+ * keyAgreement.doPhase(serverEphemeralPublicKey, true);
+ * byte[] sharedSecret = keyAgreement.generateSecret();
+ *
+ * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation
+ * // function with some other data: "salt" and "info". Salt is an optional random value,
+ * // omitted in this example. It's good practice to include both public keys and any other
+ * // key negotiation data in info. Here we use the public keys and a label that indicates
+ * // messages encrypted with this key are coming from the server.
+ * byte[] salt = {};
+ * ByteArrayOutputStream info = new ByteArrayOutputStream();
+ * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8));
+ * info.write(myKeyPair.getPublic().getEncoded());
+ * info.write(serverEphemeralPublicKey.getEncoded());
+ *
+ * // This example uses the Tink library and the HKDF key derivation function.
+ * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf(
+ *         "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32));
+ * byte[] associatedData = {};
+ * return key.decrypt(ciphertext, associatedData);
+ * }
  */
 public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
 
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 3ebca6a..293ab05 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -100,6 +100,15 @@
 
     /**
      * Purpose of key: creating a shared ECDH secret through key agreement.
+     *
+     * <p>A key having this purpose can be combined with the elliptic curve public key of another
+     * party to establish a shared secret over an insecure channel. It should be used  as a
+     * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is
+     * available <a
+     * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh"
+     * >here</a>).
+     * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this
+     * article</a> for a more detailed explanation.
      */
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
diff --git a/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/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index cb2aada..583964b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -174,6 +174,9 @@
     <!-- 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>
 
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 0372417..efc55c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -82,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) {};
     }
 
@@ -358,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 */);
@@ -367,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/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..71331df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -66,7 +66,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;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index ae53005..725f87d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -375,17 +375,29 @@
     }
 
     /**
-     * Hides the menu activity.
+     * Hides the menu view.
      */
     public void hideMenu() {
+        hideMenu(true /* animate */, true /* resize */);
+    }
+
+    /**
+     * Hides the menu view.
+     *
+     * @param animate whether to animate the menu fadeout
+     * @param resize whether or not to resize the PiP with the state change
+     */
+    public void hideMenu(boolean animate, boolean resize) {
         final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
             Log.d(TAG, "hideMenu() state=" + mMenuState
                     + " isMenuVisible=" + isMenuVisible
+                    + " animate=" + animate
+                    + " resize=" + resize
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         if (isMenuVisible) {
-            mPipMenuView.hideMenu();
+            mPipMenuView.hideMenu(animate, resize);
         }
     }
 
@@ -404,15 +416,6 @@
     }
 
     /**
-     * Preemptively mark the menu as invisible, used when we are directly manipulating the pinned
-     * stack and don't want to trigger a resize which can animate the stack in a conflicting way
-     * (ie. when manually expanding or dismissing).
-     */
-    public void hideMenuWithoutResize() {
-        onMenuStateChanged(MENU_STATE_NONE, false /* resize */, null /* callback */);
-    }
-
-    /**
      * Sets the menu actions to the actions provided by the current PiP menu.
      */
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 962c467..1cf3a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -89,7 +89,6 @@
     private static final boolean ENABLE_RESIZE_HANDLE = false;
 
     private int mMenuState;
-    private boolean mResize = true;
     private boolean mAllowMenuTimeout = true;
     private boolean mAllowTouches = true;
 
@@ -329,16 +328,21 @@
         hideMenu(null);
     }
 
+    void hideMenu(boolean animate, boolean resize) {
+        hideMenu(null, true /* notifyMenuVisibility */, animate, resize);
+    }
+
     void hideMenu(Runnable animationEndCallback) {
-        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */);
+        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */,
+                true /* resize */);
     }
 
     private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
-            boolean animate) {
+            boolean animate, boolean resize) {
         if (mMenuState != MENU_STATE_NONE) {
             cancelDelayedHide();
             if (notifyMenuVisibility) {
-                notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
+                notifyMenuStateChange(MENU_STATE_NONE, resize, null);
             }
             mMenuContainerAnimator = new AnimatorSet();
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -469,7 +473,8 @@
     private void expandPip() {
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
-        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
+        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */,
+                true /* resize */);
     }
 
     private void dismissPip() {
@@ -479,7 +484,8 @@
         final boolean animate = mMenuState != MENU_STATE_CLOSE;
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
-        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
+        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate,
+                true /* resize */);
     }
 
     private void showSettings() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index b19dcae..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/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index c815e65..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;
 
@@ -282,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;
@@ -293,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/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/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4966bfa..df66981 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -189,6 +189,13 @@
     }
 }
 
+static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong surfaceControlPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr);
+    proxy->setSurfaceControl(surfaceControl);
+}
+
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
         jlong proxyPtr) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -671,6 +678,8 @@
         {"nSetName", "(JLjava/lang/String;)V", (void*)android_view_ThreadedRenderer_setName},
         {"nSetSurface", "(JLandroid/view/Surface;Z)V",
          (void*)android_view_ThreadedRenderer_setSurface},
+        {"nSetSurfaceControl", "(JJ)V",
+         (void*)android_view_ThreadedRenderer_setSurfaceControl},
         {"nPause", "(J)Z", (void*)android_view_ThreadedRenderer_pause},
         {"nSetStopped", "(JZ)V", (void*)android_view_ThreadedRenderer_setStopped},
         {"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha},
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 65afcc3..9543d47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -133,6 +133,7 @@
 void CanvasContext::destroy() {
     stopDrawing();
     setSurface(nullptr);
+    setSurfaceControl(nullptr);
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
@@ -173,6 +174,19 @@
     setupPipelineSurface();
 }
 
+void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    if (surfaceControl == mSurfaceControl) return;
+
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (mSurfaceControl != nullptr) {
+        funcs.releaseFunc(mSurfaceControl);
+    }
+    mSurfaceControl = surfaceControl;
+    if (mSurfaceControl != nullptr) {
+        funcs.acquireFunc(mSurfaceControl);
+    }
+}
+
 void CanvasContext::setupPipelineSurface() {
     bool hasSurface = mRenderPipeline->setSurface(
             mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b31883b..917b00c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -112,6 +112,7 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
     bool hasSurface() const { return mNativeSurface.get(); }
@@ -218,6 +219,9 @@
 
     RenderThread& mRenderThread;
     std::unique_ptr<ReliableSurface> mNativeSurface;
+    // The SurfaceControl reference is passed from ViewRootImpl, can be set to
+    // NULL to remove the reference
+    ASurfaceControl* mSurfaceControl = nullptr;
     // stopped indicates the CanvasContext will reject actual redraw operations,
     // and defer repaint until it is un-stopped
     bool mStopped = false;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0ade8dd..e14842f 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -84,6 +84,19 @@
     });
 }
 
+void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (surfaceControl) {
+        funcs.acquireFunc(surfaceControl);
+    }
+    mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable {
+        mContext->setSurfaceControl(control);
+        if (control) {
+            funcs.releaseFunc(control);
+        }
+    });
+}
+
 void RenderProxy::allocateBuffers() {
     mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a4adb16..366d6b5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -20,6 +20,7 @@
 #include <SkBitmap.h>
 #include <android/native_window.h>
 #include <cutils/compiler.h>
+#include <android/surface_control.h>
 #include <utils/Functor.h>
 
 #include "../FrameMetricsObserver.h"
@@ -72,6 +73,7 @@
     void setName(const char* name);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     void allocateBuffers();
     bool pause();
     void setStopped(bool stopped);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7750a31..2610186 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -33,6 +33,7 @@
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
+#include <dlfcn.h>
 #include <sys/resource.h>
 #include <utils/Condition.h>
 #include <utils/Log.h>
@@ -49,6 +50,17 @@
 
 static JVMAttachHook gOnStartHook = nullptr;
 
+ASurfaceControlFunctions::ASurfaceControlFunctions() {
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire");
+    LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_acquire!");
+
+    releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
+    LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_release!");
+}
+
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
     int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4fbb0716..bb7c518 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -78,6 +78,16 @@
     virtual ~VsyncSource() {}
 };
 
+typedef void (*ASC_acquire)(ASurfaceControl* control);
+typedef void (*ASC_release)(ASurfaceControl* control);
+
+struct ASurfaceControlFunctions {
+    ASurfaceControlFunctions();
+
+    ASC_acquire acquireFunc;
+    ASC_release releaseFunc;
+};
+
 class ChoreographerSource;
 class DummyVsyncSource;
 
@@ -121,6 +131,10 @@
 
     void preload();
 
+    const ASurfaceControlFunctions& getASurfaceControlFunctions() {
+        return mASurfaceControlFunctions;
+    }
+
     /**
      * isCurrent provides a way to query, if the caller is running on
      * the render thread.
@@ -189,6 +203,8 @@
     sk_sp<GrDirectContext> mGrContext;
     CacheManager* mCacheManager;
     sp<VulkanManager> mVkManager;
+
+    ASurfaceControlFunctions mASurfaceControlFunctions;
 };
 
 } /* namespace renderthread */
diff --git a/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/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/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ca0d29f..2c45ed3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1356,6 +1356,7 @@
     }
 
     private void startImpl() {
+        baseStart(0); // unknown device at this point
         stayAwake(true);
         _start();
     }
@@ -1381,6 +1382,7 @@
     public void stop() throws IllegalStateException {
         stayAwake(false);
         _stop();
+        baseStop();
     }
 
     private native void _stop() throws IllegalStateException;
@@ -1394,6 +1396,7 @@
     public void pause() throws IllegalStateException {
         stayAwake(false);
         _pause();
+        basePause();
     }
 
     private native void _pause() throws IllegalStateException;
@@ -3479,7 +3482,8 @@
             case MEDIA_STOPPED:
                 {
                     tryToDisableNativeRoutingCallback();
-                    baseStop();
+                    // FIXME see b/179218630
+                    //baseStop();
                     TimeProvider timeProvider = mTimeProvider;
                     if (timeProvider != null) {
                         timeProvider.onStopped();
@@ -3489,15 +3493,17 @@
 
             case MEDIA_STARTED:
                 {
-                    baseStart(native_getRoutedDeviceId());
+                    // FIXME see b/179218630
+                    //baseStart(native_getRoutedDeviceId());
                     tryToEnableNativeRoutingCallback();
                 }
                 // fall through
             case MEDIA_PAUSED:
                 {
-                    if (msg.what == MEDIA_PAUSED) {
-                        basePause();
-                    }
+                    // FIXME see b/179218630
+                    //if (msg.what == MEDIA_PAUSED) {
+                    //    basePause();
+                    //}
                     TimeProvider timeProvider = mTimeProvider;
                     if (timeProvider != null) {
                         timeProvider.onPaused(msg.what == MEDIA_PAUSED);
diff --git a/media/java/android/media/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/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index 3056e98..7f3450b 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -69,9 +69,8 @@
 
     /**
      * Reports playback state event.
-     * @hide
      */
-    public void reportPlaybackStateEvent(PlaybackStateEvent event) {
+    public void reportPlaybackStateEvent(@NonNull PlaybackStateEvent event) {
         mManager.reportPlaybackStateEvent(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/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/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/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/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..539a81b 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" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7bfb42b..bf5198e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -194,6 +194,5 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
-        "checked-wm_shell_protolog.json",
     ],
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5af0244..2faca8d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -600,6 +600,14 @@
             android:permission="android.permission.BIND_REMOTEVIEWS"
             android:exported="false" />
 
+        <!-- ContentProvider that returns a People Tile preview for a given shortcut -->
+        <provider
+            android:name="com.android.systemui.people.PeopleProvider"
+            android:authorities="com.android.systemui.people.PeopleProvider"
+            android:exported="true"
+            android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW">
+        </provider>
+
         <!-- a gallery of delicious treats -->
         <service
             android:name=".DessertCaseDream"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index d43aaf0..beee03b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -46,6 +46,21 @@
         return true;
     }
 
+    /**
+     * @return if detail panel should animate when shown or closed
+     */
+    default boolean shouldAnimate() {
+        return true;
+    }
+
+    /**
+     * @return true if the callback handled the event and wants to keep the detail panel open, false
+     * otherwise. Returning false will close the panel.
+     */
+    default boolean onDoneButtonClicked() {
+        return false;
+    }
+
     default UiEventLogger.UiEventEnum openDetailEvent() {
         return INVALID;
     }
diff --git a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
new file mode 100644
index 0000000..51c442a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight"
+        android:radius="40dp"/>
diff --git a/packages/SystemUI/res/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/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..78927f8 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>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 594fbdf..0d92aea 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>
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..32381be 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1341,6 +1341,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>
 
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..825ea25 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,6 +167,20 @@
     }
 
     /**
+     * By default, the new layout will be enabled. When false, revert to the old style.
+     */
+    public void setIsNewLayoutEnabled(boolean isEnabled) {
+        if (!isEnabled) {
+            for (int i = 0; i < mButtons.length; i++) {
+                mButtons[i].disableNewLayout();
+            }
+            mDeleteButton.disableNewLayout();
+            mOkButton.disableNewLayout();
+            reloadColors();
+        }
+    }
+
+    /**
      * Reload colors from resources.
      **/
     public void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f247948..1b5aa45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -71,9 +71,10 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker);
+                messageAreaControllerFactory, latencyTracker, emergencyButtonController);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index c0aa2af..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,11 +34,13 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            FalsingCollector falsingCollector) {
+            EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,7 +23,6 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -49,24 +48,27 @@
     private final boolean mIsPukScreenAvailable;
 
     private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Inject
-    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mIsPukScreenAvailable = resources.getBoolean(
                 com.android.internal.R.bool.config_enable_puk_unlock_screen);
         mLockPatternUtils = lockPatternUtils;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
     public SecurityMode getSecurityMode(int userId) {
-        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
         if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
             return SecurityMode.SimPin;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
     private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
             new ArrayList<>();
     private final LayoutInflater mLayoutInflater;
+    private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
     private final Factory mKeyguardSecurityViewControllerFactory;
 
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
-            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
+            EmergencyButtonController.Factory emergencyButtonControllerFactory) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
+        mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
     }
 
     @Override
@@ -111,7 +114,8 @@
 
         if (childController == null) {
             childController = new NullKeyguardInputViewController(
-                    securityMode, keyguardSecurityCallback);
+                    securityMode, keyguardSecurityCallback,
+                    mEmergencyButtonControllerFactory.create(null));
         }
 
         return childController;
@@ -140,8 +144,9 @@
     private static class NullKeyguardInputViewController
             extends KeyguardInputViewController<KeyguardInputView> {
         protected NullKeyguardInputViewController(SecurityMode securityMode,
-                KeyguardSecurityCallback keyguardSecurityCallback) {
-            super(null, securityMode, keyguardSecurityCallback);
+                KeyguardSecurityCallback keyguardSecurityCallback,
+                EmergencyButtonController emergencyButtonController) {
+            super(null, securityMode, keyguardSecurityCallback, emergencyButtonController);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index b218141..4d2ebbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -78,13 +78,15 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 890a17c..0d9bb6f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 
@@ -85,13 +84,15 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
@@ -196,8 +197,7 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
         } else {
-            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
-                    .getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
             if (info != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index fb97a30..1fbf71d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.FileDescriptor;
@@ -317,6 +315,22 @@
                 R.dimen.widget_label_font_size);
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
+
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged();
+            }
+        }
+    }
+
+    void onOverlayChanged() {
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onOverlayChanged();
+            }
+        }
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -479,8 +493,7 @@
      * Representation of an item that appears under the clock on main keyguard message.
      */
     @VisibleForTesting
-    static class KeyguardSliceTextView extends TextView implements
-            ConfigurationController.ConfigurationListener {
+    static class KeyguardSliceTextView extends TextView {
         private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
         @StyleRes
@@ -492,24 +505,10 @@
             setEllipsize(TruncateAt.END);
         }
 
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-            Dependency.get(ConfigurationController.class).addCallback(this);
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-            Dependency.get(ConfigurationController.class).removeCallback(this);
-        }
-
-        @Override
         public void onDensityOrFontScaleChanged() {
             updatePadding();
         }
 
-        @Override
         public void onOverlayChanged() {
             setTextAppearance(sStyleId);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
         public void onDensityOrFontScaleChanged() {
             mView.onDensityOrFontScaleChanged();
         }
+        @Override
+        public void onOverlayChanged() {
+            mView.onOverlayChanged();
+        }
     };
 
     Observer<Slice> mObserver = new Observer<Slice>() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 import java.io.FileDescriptor;
@@ -56,7 +55,6 @@
     private final IActivityManager mIActivityManager;
 
     private TextView mLogoutView;
-    private boolean mCanShowLogout = true; // by default, try to show the logout button here
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
     private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
@@ -130,11 +128,6 @@
         }
     }
 
-    void setCanShowLogout(boolean canShowLogout) {
-        mCanShowLogout = canShowLogout;
-        updateLogoutView();
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -159,10 +152,7 @@
         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
         onSliceContentChanged();
 
-        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setEnableMarquee(shouldMarquee);
         updateOwnerInfo();
-        updateLogoutView();
         updateDark();
     }
 
@@ -209,11 +199,11 @@
         return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
     }
 
-    void updateLogoutView() {
+    void updateLogoutView(boolean shouldShowLogout) {
         if (mLogoutView == null) {
             return;
         }
-        mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE);
+        mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
         // Logout button will stay in language of user 0 if we don't set that manually.
         mLogoutView.setText(mContext.getResources().getString(
                 com.android.internal.R.string.global_action_logout));
@@ -313,11 +303,6 @@
         }
     }
 
-    private boolean shouldShowLogout() {
-        return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
-                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-    }
-
     private void onLogoutClicked(View view) {
         int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
         try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 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/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3728106..886c372 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -16,17 +16,23 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
 
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
 /**
  * Similar to the {@link NumPadKey}, but displays an image.
  */
 public class NumPadButton extends AlphaOptimizedImageButton {
 
-    private final NumPadAnimator mAnimator;
+    private NumPadAnimator mAnimator;
 
     public NumPadButton(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -36,25 +42,34 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams());
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
+        super.setLayoutParams(params);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        // Set width/height to the same value to ensure a smooth circle for the bg
-        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+        // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+        // the height to match the old pin bouncer
+        int width = getMeasuredWidth();
+        int height = mAnimator == null ? (int) (width * .75f) : width;
+
+        setMeasuredDimension(getMeasuredWidth(), height);
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        mAnimator.onLayout(b - t);
+        if (mAnimator != null) mAnimator.onLayout(b - t);
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        mAnimator.start();
+        if (mAnimator != null) mAnimator.start();
         return super.onTouchEvent(event);
     }
 
@@ -62,6 +77,23 @@
      * Reload colors from resources.
      **/
     public void reloadColors() {
-        mAnimator.reloadColors(getContext());
+        if (mAnimator != null) {
+            mAnimator.reloadColors(getContext());
+        } else {
+            // Needed for old style pin
+            int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)
+                    .getDefaultColor();
+            ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
+        }
+    }
+
+    /**
+     * By default, the new layout will be enabled. Invoking will revert to the old style
+     */
+    public void disableNewLayout() {
+        mAnimator = null;
+        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+        setBackground(getContext().getResources().getDrawable(
+                R.drawable.ripple_drawable_pin, ctw.getTheme()));
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 756d610..01e1c63 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -22,6 +22,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -47,7 +48,7 @@
     private int mTextViewResId;
     private PasswordTextView mTextView;
 
-    private final NumPadAnimator mAnimator;
+    private NumPadAnimator mAnimator;
 
     private View.OnClickListener mListener = new View.OnClickListener() {
         @Override
@@ -131,6 +132,17 @@
     }
 
     /**
+     * By default, the new layout will be enabled. Invoking will revert to the old style
+     */
+    public void disableNewLayout() {
+        findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
+        mAnimator = null;
+        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+        setBackground(getContext().getResources().getDrawable(
+                R.drawable.ripple_drawable_pin, ctw.getTheme()));
+    }
+
+    /**
      * Reload colors from resources.
      **/
     public void reloadColors() {
@@ -141,7 +153,7 @@
         mDigitText.setTextColor(textColor);
         mKlondikeText.setTextColor(klondikeColor);
 
-        mAnimator.reloadColors(getContext());
+        if (mAnimator != null) mAnimator.reloadColors(getContext());
     }
 
     @Override
@@ -150,14 +162,14 @@
             doHapticKeyClick();
         }
 
-        mAnimator.start();
+        if (mAnimator != null) mAnimator.start();
 
         return super.onTouchEvent(event);
     }
 
     @Override
     public void setLayoutParams(ViewGroup.LayoutParams params) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
+        if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
         super.setLayoutParams(params);
     }
@@ -167,8 +179,12 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         measureChildren(widthMeasureSpec, heightMeasureSpec);
 
-        // Set width/height to the same value to ensure a smooth circle for the bg
-        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+        // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+        // the height to match the old pin bouncer
+        int width = getMeasuredWidth();
+        int height = mAnimator == null ? (int) (width * .75f) : width;
+
+        setMeasuredDimension(getMeasuredWidth(), height);
     }
 
     @Override
@@ -187,7 +203,7 @@
         left = centerX - mKlondikeText.getMeasuredWidth() / 2;
         mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
 
-        mAnimator.onLayout(b - t);
+        if (mAnimator != null) mAnimator.onLayout(b - t);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/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/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 71ec33e..b6a232d 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui;
 
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -26,13 +29,16 @@
 import android.util.Size;
 import android.view.SurfaceHolder;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.glwallpaper.EglHelper;
-import com.android.systemui.glwallpaper.GLWallpaperRenderer;
 import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 
@@ -45,8 +51,13 @@
     // We delayed destroy render context that subsequent render requests have chance to cancel it.
     // This is to avoid destroying then recreating render context in a very short time.
     private static final int DELAY_FINISH_RENDERING = 1000;
+    private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
     private static final boolean DEBUG = false;
+    private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
     private HandlerThread mWorker;
+    // scaled down version
+    private Bitmap mMiniBitmap;
 
     @Inject
     public ImageWallpaper() {
@@ -70,6 +81,7 @@
         super.onDestroy();
         mWorker.quitSafely();
         mWorker = null;
+        mMiniBitmap = null;
     }
 
     class GLEngine extends Engine {
@@ -80,7 +92,7 @@
         @VisibleForTesting
         static final int MIN_SURFACE_HEIGHT = 64;
 
-        private GLWallpaperRenderer mRenderer;
+        private ImageWallpaperRenderer mRenderer;
         private EglHelper mEglHelper;
         private final Runnable mFinishRenderingTask = this::finishRendering;
         private boolean mNeedRedraw;
@@ -101,6 +113,12 @@
             setFixedSizeAllowed(true);
             setOffsetNotificationsEnabled(false);
             updateSurfaceSize();
+            mMiniBitmap = null;
+            if (mWorker == null || mWorker.getThreadHandler() == null) {
+                updateMiniBitmap();
+            } else {
+                mWorker.getThreadHandler().post(this::updateMiniBitmap);
+            }
         }
 
         EglHelper getEglHelperInstance() {
@@ -111,6 +129,20 @@
             return new ImageWallpaperRenderer(getDisplayContext());
         }
 
+        private void updateMiniBitmap() {
+            mRenderer.useBitmap(b -> {
+                int size = Math.min(b.getWidth(), b.getHeight());
+                float scale = 1.0f;
+                if (size > MIN_SURFACE_WIDTH) {
+                    scale = (float) MIN_SURFACE_WIDTH / (float) size;
+                }
+                mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
+                        Math.round(scale * b.getHeight()), false);
+                computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
+                mLocalColorsToAdd.clear();
+            });
+        }
+
         private void updateSurfaceSize() {
             SurfaceHolder holder = getSurfaceHolder();
             Size frameSize = mRenderer.reportSurfaceSize();
@@ -126,6 +158,7 @@
 
         @Override
         public void onDestroy() {
+            mMiniBitmap = null;
             mWorker.getThreadHandler().post(() -> {
                 mRenderer.finish();
                 mRenderer = null;
@@ -134,6 +167,61 @@
             });
         }
 
+
+
+        @Override
+        public boolean supportsLocalColorExtraction() {
+            return true;
+        }
+
+        @Override
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            mWorker.getThreadHandler().post(() -> {
+                Bitmap bitmap = mMiniBitmap;
+                if (bitmap == null) {
+                    mLocalColorsToAdd.addAll(regions);
+                } else {
+                    computeAndNotifyLocalColors(regions, bitmap);
+                }
+            });
+        }
+
+        private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
+            List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
+            try {
+                notifyLocalColorsChanged(regions, colors);
+            } catch (RuntimeException e) {
+                Log.e(TAG, e.getMessage(), e);
+            }
+        }
+
+        @Override
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            // No-OP
+        }
+
+        private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
+                Bitmap b) {
+            List<WallpaperColors> colors = new ArrayList<>(areas.size());
+            for (int i = 0; i < areas.size(); i++) {
+                RectF area = areas.get(i);
+                if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
+                    colors.add(null);
+                    continue;
+                }
+                Rect subImage = new Rect(
+                        Math.round(area.left * b.getWidth()),
+                        Math.round(area.top * b.getHeight()),
+                        Math.round(area.right * b.getWidth()),
+                        Math.round(area.bottom * b.getHeight()));
+                Bitmap colorImg = Bitmap.createBitmap(b,
+                        subImage.left, subImage.top, subImage.width(), subImage.height());
+                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+                colors.add(color);
+            }
+            return colors;
+        }
+
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
             if (mWorker == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 38a82f8..36937d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,10 +16,19 @@
 
 package com.android.systemui.controls.dagger
 
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
@@ -28,15 +37,43 @@
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
  *
  * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
- * instantiated if `featureEnabled` is true.
+ * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
  */
 @SysUISingleton
 class ControlsComponent @Inject constructor(
     @ControlsFeatureEnabled private val featureEnabled: Boolean,
+    private val context: Context,
     private val lazyControlsController: Lazy<ControlsController>,
     private val lazyControlsUiController: Lazy<ControlsUiController>,
-    private val lazyControlsListingController: Lazy<ControlsListingController>
+    private val lazyControlsListingController: Lazy<ControlsListingController>,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    private val secureSettings: SecureSettings
 ) {
+
+    private val contentResolver: ContentResolver
+        get() = context.contentResolver
+
+    private var canShowWhileLockedSetting = false
+
+    val showWhileLockedObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            updateShowWhileLocked()
+        }
+    }
+
+    init {
+        if (featureEnabled) {
+            secureSettings.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
+                false, /* notifyForDescendants */
+                showWhileLockedObserver
+            )
+            updateShowWhileLocked()
+        }
+    }
+
     fun getControlsController(): Optional<ControlsController> {
         return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
     }
@@ -52,4 +89,37 @@
             Optional.empty()
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * @return true if controls are feature-enabled and have available services to serve controls
+     */
+    fun isEnabled() = featureEnabled && lazyControlsController.get().available
+
+    /**
+     * Returns one of 3 states:
+     * * AVAILABLE - Controls can be made visible
+     * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock
+     * * UNAVAILABLE - Controls are not enabled
+     */
+    fun getVisibility(): Visibility {
+        if (!isEnabled()) return Visibility.UNAVAILABLE
+        if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+                == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
+            return Visibility.AVAILABLE_AFTER_UNLOCK
+        }
+        if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+            return Visibility.AVAILABLE_AFTER_UNLOCK
+        }
+
+        return Visibility.AVAILABLE
+    }
+
+    private fun updateShowWhileLocked() {
+        canShowWhileLockedSetting = secureSettings.getInt(
+            Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0
+    }
+
+    enum class Visibility {
+        AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ffb8446..91c2dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
@@ -146,4 +147,9 @@
      * Member injection into the supplied argument.
      */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(ClockOptionsProvider clockOptionsProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b0067cd..b67db03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -22,6 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.clock.ClockModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
@@ -90,6 +91,7 @@
 @Module(includes = {
             AppOpsModule.class,
             AssistModule.class,
+            ClockModule.class,
             ControlsModule.class,
             DemoModeModule.class,
             FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 8af45a5..d85b101 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,8 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 
 import android.animation.Animator;
@@ -250,6 +252,7 @@
     private final IWindowManager mIWindowManager;
     private final Executor mBackgroundExecutor;
     private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
+    private ControlsComponent mControlsComponent;
     private Optional<ControlsController> mControlsControllerOptional;
     private final RingerModeTracker mRingerModeTracker;
     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
@@ -338,6 +341,7 @@
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mControlsComponent = controlsComponent;
         mControlsUiControllerOptional = controlsComponent.getControlsUiController();
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
@@ -387,7 +391,8 @@
                     if (mDialog.mWalletViewController != null) {
                         mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
                     }
-                    if (!mDialog.isShowingControls() && shouldShowControls()) {
+                    if (!mDialog.isShowingControls()
+                            && mControlsComponent.getVisibility() == AVAILABLE) {
                         mDialog.showControls(mControlsUiControllerOptional.get());
                     }
                     if (unlocked) {
@@ -397,14 +402,15 @@
             }
         });
 
-        if (controlsComponent.getControlsListingController().isPresent()) {
-            controlsComponent.getControlsListingController().get()
+        if (mControlsComponent.getControlsListingController().isPresent()) {
+            mControlsComponent.getControlsListingController().get()
                     .addCallback(list -> {
                         mControlsServiceInfos = list;
                         // This callback may occur after the dialog has been shown. If so, add
                         // controls into the already visible space or show the lock msg if needed.
                         if (mDialog != null) {
-                            if (!mDialog.isShowingControls() && shouldShowControls()) {
+                            if (!mDialog.isShowingControls()
+                                    && mControlsComponent.getVisibility() == AVAILABLE) {
                                 mDialog.showControls(mControlsUiControllerOptional.get());
                             } else if (shouldShowLockMessage(mDialog)) {
                                 mDialog.showLockMessage();
@@ -704,7 +710,7 @@
 
         mDepthController.setShowingHomeControls(true);
         ControlsUiController uiController = null;
-        if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
+        if (mControlsComponent.getVisibility() == AVAILABLE) {
             uiController = mControlsUiControllerOptional.get();
         }
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
@@ -2687,26 +2693,24 @@
         return isPanelDebugModeEnabled(context);
     }
 
-    private boolean shouldShowControls() {
-        boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils
-                .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT;
-        return controlsAvailable()
-                && (mKeyguardStateController.isUnlocked() || showOnLockScreen);
-    }
-
     private boolean controlsAvailable() {
         return mDeviceProvisioned
-                && mControlsUiControllerOptional.isPresent()
-                && mControlsUiControllerOptional.get().getAvailable()
+                && mControlsComponent.isEnabled()
                 && !mControlsServiceInfos.isEmpty();
     }
 
     private boolean shouldShowLockMessage(ActionsDialog dialog) {
+        return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
+                || isWalletAvailableAfterUnlock(dialog);
+    }
+
+    // Temporary while we move items out of the power menu
+    private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
         boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT;
         return !mKeyguardStateController.isUnlocked()
                 && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
-                && (controlsAvailable() || dialog.isWalletViewAvailable());
+                && dialog.isWalletViewAvailable();
     }
 
     private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 1a0356c..01a353c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -58,6 +58,14 @@
         mWallpaper = new ImageGLWallpaper(mProgram);
     }
 
+    /**
+     * @hide
+     * @return
+     */
+    public void useBitmap(Consumer<Bitmap> c) {
+        mTexture.use(c);
+    }
+
     @Override
     public boolean isWcgContent() {
         return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/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/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..cbcbd3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -49,6 +49,7 @@
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -286,7 +287,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,7 +439,7 @@
     }
 
     /** Creates a {@link RemoteViews} for {@code tile}. */
-    private static RemoteViews createRemoteViews(Context context,
+    public static RemoteViews createRemoteViews(Context context,
             PeopleSpaceTile tile, int appWidgetId) {
         RemoteViews views;
         if (tile.getNotificationKey() != null) {
@@ -455,6 +456,9 @@
             PeopleSpaceTile tile, int appWidgetId) {
         try {
             views.setTextViewText(R.id.name, tile.getUserName().toString());
+            views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
+
             views.setImageViewBitmap(
                     R.id.package_icon,
                     PeopleSpaceUtils.convertDrawableToBitmap(
@@ -462,8 +466,6 @@
                                     tile.getPackageName())
                     )
             );
-            views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
 
             Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
             activityIntent.addFlags(
@@ -612,11 +614,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) {
@@ -701,7 +719,7 @@
      * </li>
      */
     public static boolean shouldKeepConversation(PeopleSpaceTile tile) {
-        return tile != null && tile.getUserName().length() != 0;
+        return tile != null && !TextUtils.isEmpty(tile.getUserName());
     }
 
     private static boolean hasBirthdayStatus(PeopleSpaceTile tile, Context context) {
@@ -792,8 +810,7 @@
     private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
             Context context, int appWidgetId, PeopleSpaceTile tile) {
         updateAppWidgetOptions(appWidgetManager, appWidgetId, tile);
-        RemoteViews views = createRemoteViews(context,
-                tile, appWidgetId);
+        RemoteViews views = createRemoteViews(context, tile, appWidgetId);
         appWidgetManager.updateAppWidget(appWidgetId, views);
     }
 
@@ -866,4 +883,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/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/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/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/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..8aadef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,10 @@
 
 import static android.view.View.GONE;
 
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -66,6 +70,8 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintSet;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -77,6 +83,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 +103,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 +143,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 +303,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 +325,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 +340,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 +368,7 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardQsUserSwitchEnabled;
     private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
@@ -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,
@@ -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(
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/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/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/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 0c69ffd..10332bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -100,6 +100,7 @@
         return new ImageWallpaper() {
             @Override
             public Engine onCreateEngine() {
+                onCreate();
                 return new GLEngine(mHandler) {
                     @Override
                     public Context getDisplayContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/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/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/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/services/Android.bp b/services/Android.bp
index 61591c2..3154628 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -33,6 +33,7 @@
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
+        ":services.texttospeech-sources",
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
@@ -83,6 +84,7 @@
         "services.startop",
         "services.systemcaptions",
         "services.translation",
+        "services.texttospeech",
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
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/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/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..154e183 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,10 @@
 
     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.
+        final NetworkRequestInfo nri = getNriForAppRequest(request);
 
         if (nri != null) {
             if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
@@ -3770,8 +3770,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 +3781,6 @@
 
     private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
-
         nri.unlinkDeathRecipient();
         for (final NetworkRequest req : nri.mRequests) {
             mNetworkRequests.remove(req);
@@ -3806,6 +3803,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 +4983,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 +4996,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 +5120,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 +5150,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 +5170,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 +5195,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 +5373,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 +5413,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 +5438,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 +5755,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 +6928,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 +8870,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 +8882,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 +8971,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/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..7f0b962 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -568,7 +568,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(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bedd19b..0b1c115 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -186,6 +186,7 @@
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -5626,6 +5627,23 @@
                 ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            final int modeFlags, IBinder callerToken) {
+        final int size = uris.size();
+        int[] res = new int[size];
+        // Default value DENIED.
+        Arrays.fill(res, PackageManager.PERMISSION_DENIED);
+
+        for (int i = 0; i < size; i++) {
+            final Uri uri = uris.get(i);
+            final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+            res[i] = checkUriPermission(ContentProvider.getUriWithoutUserId(uri), pid, uid,
+                    modeFlags, userId, callerToken);
+        }
+        return res;
+    }
+
     /**
      * @param uri This uri must NOT contain an embedded userId.
      * @param userId The userId in which the uri is to be resolved.
@@ -10747,6 +10765,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/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/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/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 0b78dd0..a4b3ac5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -46,7 +46,7 @@
 
 /**
  * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 1a7544fc..fc1200a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -41,7 +41,7 @@
 
 /**
  * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
 
@@ -103,18 +103,9 @@
             disabledFeatures.add(disabledFeature);
         }
 
-        android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 =
-                android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(getFreshDaemon());
         try {
-            final int status;
-            if (daemon11 != null) {
-                status = daemon11.enroll_1_1(token, mTimeoutSec, disabledFeatures, mSurfaceHandle);
-            } else if (mSurfaceHandle == null) {
-                status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
-            } else {
-                Slog.e(TAG, "enroll(): surface is only supported in @1.1 HAL");
-                status = BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-            }
+            final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
+
             if (status != Status.OK) {
                 onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
                 mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index c3d54c2..72c5ee5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -29,8 +29,7 @@
 
 /**
  * Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 722a3b8..b1083d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index abfda49..1e3b92d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -33,8 +33,7 @@
 
 /**
  * Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index 9a0974b..f2a9afc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -32,8 +32,7 @@
 
 /**
  * Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
     private static final String TAG = "FaceInternalEnumerateClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index acae899..d63791c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
     private static final String TAG = "FaceRemovalClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 14a4648..9d977d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -30,7 +30,7 @@
 
 /**
  * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index e5edfaf..28580de 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -27,7 +27,7 @@
 
 /**
  * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index 6290e00..cc3d8f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index 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/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/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/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c4c0f68..bd577f3 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -371,6 +371,9 @@
             int durationMs) {
         Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
                 + durationMs + "ms");
+        if (mServiceNameResolver == null) {
+            return;
+        }
         enforceCallingPermissionForManagement();
 
         Objects.requireNonNull(componentName);
@@ -404,6 +407,9 @@
         enforceCallingPermissionForManagement();
 
         synchronized (mLock) {
+            if (mServiceNameResolver == null) {
+                return false;
+            }
             final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
             if (!changed) {
                 if (verbose) {
@@ -434,6 +440,10 @@
     public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
         enforceCallingPermissionForManagement();
 
+        if (mServiceNameResolver == null) {
+            return false;
+        }
+
         synchronized (mLock) {
             return mServiceNameResolver.isDefaultServiceEnabled(userId);
         }
@@ -958,6 +968,10 @@
             public void onPackageModified(String packageName) {
                 if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
 
+                if (mServiceNameResolver == null) {
+                    return;
+                }
+
                 final int userId = getChangingUserId();
                 final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
                 if (serviceName == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 47cb43e..5eec315 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();
         }
@@ -1451,6 +1473,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/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index cc510fb..d3c853d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -19,11 +19,11 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
+import android.annotation.Nullable;
 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;
@@ -39,6 +39,7 @@
 
 import com.android.server.location.ClientBrokerProto;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Set;
@@ -67,7 +68,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 +96,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.
      */
@@ -176,9 +183,9 @@
     }
 
     /* package */ ContextHubClientBroker(
-            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
-            ContextHubInfo contextHubInfo, short hostEndPointId,
-            IContextHubClientCallback callback) {
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
@@ -187,15 +194,17 @@
         mCallbackInterface = callback;
         mPendingIntentRequest = new PendingIntentRequest();
         mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+        mAttributionTag = attributionTag;
 
         mHasAccessContextHubPermission = context.checkCallingPermission(
                 Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
     }
 
     /* package */ ContextHubClientBroker(
-            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
-            ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
-            long nanoAppId) {
+            Context context, IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+            short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
+            String attributionTag) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
@@ -203,6 +212,7 @@
         mHostEndPointId = hostEndPointId;
         mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
         mPackage = pendingIntent.getCreatorPackage();
+        mAttributionTag = attributionTag;
 
         mHasAccessContextHubPermission = context.checkCallingPermission(
                 Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
@@ -227,7 +237,10 @@
 
             int contextHubId = mAttachedContextHubInfo.getId();
             try {
-                result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
+                // TODO(166846988): Fill in host permissions before sending a message.
+                result = mContextHubProxy.sendMessageToHub(
+                        contextHubId, messageToNanoApp,
+                        new ArrayList<String>() /* hostPermissions */);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
                         + contextHubId + ")", e);
@@ -263,6 +276,21 @@
     }
 
     /**
+     * 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
      */
     /* package */ int getAttachedContextHubId() {
@@ -508,6 +536,9 @@
         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());
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..0351edb 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;
@@ -71,7 +70,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.
@@ -138,7 +137,7 @@
     }
 
     /* package */ ContextHubClientManager(
-            Context context, IContexthub contextHubProxy) {
+            Context context, IContextHubWrapper contextHubProxy) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
     }
@@ -148,19 +147,21 @@
      *
      * @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) {
         ContextHubClientBroker broker;
         synchronized (this) {
             short hostEndPointId = getHostEndPointId();
             broker = new ContextHubClientBroker(
                     mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                    hostEndPointId, clientCallback);
+                    hostEndPointId, clientCallback, attributionTag);
             mHostEndPointIdToClientMap.put(hostEndPointId, broker);
             mRegistrationRecordDeque.add(
                     new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -185,13 +186,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) {
         ContextHubClientBroker broker;
         String registerString = "Regenerated";
         synchronized (this) {
@@ -201,11 +204,15 @@
                 short hostEndPointId = getHostEndPointId();
                 broker = new ContextHubClientBroker(
                         mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
-                        hostEndPointId, pendingIntent, nanoAppId);
+                        hostEndPointId, pendingIntent, nanoAppId, attributionTag);
                 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);
             }
         }
 
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..2eafe6a 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;
@@ -53,6 +53,7 @@
 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;
@@ -137,7 +138,9 @@
 
         @Override
         public void handleClientMsg(ContextHubMsg message) {
-            handleClientMessageCallback(mContextHubId, message);
+            handleClientMessageCallback(mContextHubId, message,
+                    Collections.emptyList() /* nanoappPermissions */,
+                    Collections.emptyList() /* messageContentPermissions */);
         }
 
         @Override
@@ -156,7 +159,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> messageContentPermissions) {
+            handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
+                    messageContentPermissions);
+        }
+
+        @Override
+        public void handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList) {
             handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
         }
     }
@@ -174,30 +191,31 @@
             return;
         }
 
-        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper.getHub());
+        mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
         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));
         mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
 
         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 */);
             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 = "
@@ -596,7 +614,9 @@
      * @param contextHubId the ID of the hub the message came from
      * @param message      the message contents
      */
-    private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+    private void handleClientMessageCallback(
+            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+            List<String> messageContentPermissions) {
         mClientManager.onMessageFromNanoApp(contextHubId, message);
     }
 
@@ -721,7 +741,7 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, clientCallback);
+        return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
     }
 
     /**
@@ -745,7 +765,8 @@
         }
 
         ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
-        return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
+        return mClientManager.registerClient(
+                contextHubInfo, pendingIntent, nanoAppId, attributionTag);
     }
 
     /**
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/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c11e289..c1d63dd 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,24 @@
     }
 
     /**
+     * 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,
+            ArrayList<String> hostPermissions) throws RemoteException;
+
+    /**
      * @return A valid instance of Contexthub HAL 1.0.
      */
     public abstract android.hardware.contexthub.V1_0.IContexthub getHub();
@@ -148,6 +172,21 @@
             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,
+                ArrayList<String> hostPermissions) throws RemoteException {
+            return mHub.sendMessageToHub(hubId, message);
+        }
+
         public android.hardware.contexthub.V1_0.IContexthub getHub() {
             return mHub;
         }
@@ -188,6 +227,21 @@
             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,
+                ArrayList<String> hostPermissions) throws RemoteException {
+            return mHub.sendMessageToHub(hubId, message);
+        }
+
         public android.hardware.contexthub.V1_0.IContexthub getHub() {
             return mHub;
         }
@@ -227,13 +281,42 @@
         }
     }
 
-    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,
+                ArrayList<String> hostPermissions) throws RemoteException {
+            android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
+                    new android.hardware.contexthub.V1_2.ContextHubMsg();
+            newMessage.msg_1_0 = message;
+            newMessage.permissions = hostPermissions;
+            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..6a89079
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceIdleHelper extends DeviceIdleHelper {
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+
+    private @Nullable BroadcastReceiver mReceiver;
+
+    public SystemDeviceIdleHelper(Context context) {
+        mContext = context;
+        mPowerManager = context.getSystemService(PowerManager.class);
+    }
+
+    @Override
+    protected void registerInternal() {
+        if (mReceiver == null) {
+            mReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    notifyDeviceIdleChanged();
+                }
+            };
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                    FgThread.getHandler());
+        }
+    }
+
+    @Override
+    protected void unregisterInternal() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    @Override
+    public boolean isDeviceIdle() {
+        return mPowerManager.isDeviceIdleMode();
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
new file mode 100644
index 0000000..6f0e681
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+
+import java.util.Objects;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
+
+    private final DeviceIdleInternal mDeviceIdle;
+
+    public SystemDeviceStationaryHelper() {
+        mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
+    }
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.registerStationaryListener(listener);
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.unregisterStationaryListener(listener);
+    }
+}
diff --git a/services/core/java/com/android/server/location/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/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..76bdf44 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
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06e84d..d3a56c6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,6 +22,7 @@
 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;
@@ -1170,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/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..86a92d79 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.verify.domain;
 
+import static java.util.Collections.emptyList;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -26,6 +28,8 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
@@ -35,6 +39,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -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,211 @@
         mConnection.scheduleWriteSettings();
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * Resolving an Intent to an approved app happens in stages:
+     * <ol>
+     *     <li>Find all non-zero approved packages for the {@link Intent}'s domain</li>
+     *     <li>Filter to packages with the highest approval level, see {@link ApprovalLevel}</li>
+     *     <li>Filter out {@link ResolveInfo}s that don't match that approved packages</li>
+     *     <li>Take the approved packages with the latest install time</li>
+     *     <li>Take the ResolveInfo representing the Activity declared last in the manifest</li>
+     *     <li>Return remaining results if any exist</li>
+     * </ol>
+     */
+    @NonNull
     @Override
-    public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+    public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        String domain = intent.getData().getHost();
+
+        // Collect package names
+        ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+        int infosSize = infos.size();
+        for (int index = 0; index < infosSize; index++) {
+            packageApprovals.put(infos.get(index).getComponentInfo().packageName,
+                    APPROVAL_LEVEL_NONE);
+        }
+
+        // Find all approval levels
+        int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+                pkgSettingFunction);
+        if (highestApproval == APPROVAL_LEVEL_NONE) {
+            return Pair.create(emptyList(), highestApproval);
+        }
+
+        // Filter to highest, non-zero packages
+        ArraySet<String> approvedPackages = new ArraySet<>();
+        for (int index = 0; index < infosSize; index++) {
+            if (packageApprovals.valueAt(index) == highestApproval) {
+                approvedPackages.add(packageApprovals.keyAt(index));
+            }
+        }
+
+        ArraySet<String> filteredPackages = new ArraySet<>();
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // To maintain legacy behavior while the Settings API is not implemented,
+            // show the chooser if all approved apps are marked ask, skipping the
+            // last app, last declaration filtering.
+            filteredPackages.addAll(approvedPackages);
+        } else {
+            // Filter to last installed package
+            long latestInstall = Long.MIN_VALUE;
+            int approvedSize = approvedPackages.size();
+            for (int index = 0; index < approvedSize; index++) {
+                String packageName = approvedPackages.valueAt(index);
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+                long installTime = pkgSetting.getFirstInstallTime();
+                if (installTime > latestInstall) {
+                    latestInstall = installTime;
+                    filteredPackages.clear();
+                    filteredPackages.add(packageName);
+                } else if (installTime == latestInstall) {
+                    filteredPackages.add(packageName);
+                }
+            }
+        }
+
+        // Filter to approved ResolveInfos
+        ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
+        for (int index = 0; index < infosSize; index++) {
+            ResolveInfo info = infos.get(index);
+            String packageName = info.getComponentInfo().packageName;
+            if (filteredPackages.contains(packageName)) {
+                List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
+                if (infosPerPackage == null) {
+                    infosPerPackage = new ArrayList<>();
+                    approvedInfos.put(packageName, infosPerPackage);
+                }
+                infosPerPackage.add(info);
+            }
+        }
+
+        List<ResolveInfo> finalList;
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // If legacy ask, skip the last declaration filtering
+            finalList = new ArrayList<>();
+            int size = approvedInfos.size();
+            for (int index = 0; index < size; index++) {
+                finalList.addAll(approvedInfos.valueAt(index));
+            }
+        } else {
+            // Find the last declared ResolveInfo per package
+            finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+        }
+
+        return Pair.create(finalList, highestApproval);
+    }
+
+    /**
+     * @return highest approval level found
+     */
+    private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+            @NonNull String domain, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = APPROVAL_LEVEL_NONE;
+        int size = inputMap.size();
+        for (int index = 0; index < size; index++) {
+            String packageName = inputMap.keyAt(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+                continue;
+            }
+            int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+            highestApproval = Math.max(highestApproval, approval);
+            inputMap.setValueAt(index, approval);
+        }
+
+        return highestApproval;
+    }
+
+    @NonNull
+    private List<ResolveInfo> filterToLastDeclared(
+            @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
+
+        int inputSize = inputMap.size();
+        for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
+            String packageName = inputMap.keyAt(inputIndex);
+            List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                continue;
+            }
+
+            ResolveInfo result = null;
+            int highestIndex = -1;
+            int infosSize = infos.size();
+            for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
+                ResolveInfo info = infos.get(infoIndex);
+                List<ParsedActivity> activities = pkg.getActivities();
+                int activitiesSize = activities.size();
+                for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+                    if (Objects.equals(activities.get(activityIndex).getComponentName(),
+                            info.getComponentInfo().getComponentName())) {
+                        if (activityIndex > highestIndex) {
+                            highestIndex = activityIndex;
+                            result = info;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            // Shouldn't be null, but might as well be safe
+            if (result != null) {
+                finalList.add(result);
+            }
+        }
+
+        return finalList;
+    }
+
+    @Override
+    public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
             @UserIdInt int userId) {
         String packageName = pkgSetting.name;
         if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, intent, userId, false, "not valid intent");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
 
-        String host = intent.getData().getHost();
+        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
+    }
+
+    /**
+     * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
+     *                    otherwise.
+     */
+    private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
+            @UserIdInt int userId, @NonNull Object debugObject) {
+        String packageName = pkgSetting.name;
         final AndroidPackage pkg = pkgSetting.getPkg();
 
         // Should never be null, but if it is, skip this and assume that v2 is enabled
-        if (pkg != null) {
-            // To allow an instant app to immediately open domains after being installed by the
-            // user, auto approve them for any declared autoVerify domains.
-            if (pkgSetting.getInstantApp(userId)
-                    && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
-                return true;
-            }
-
-            if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
-                int legacyState = mLegacySettings.getUserState(packageName, userId);
-                switch (legacyState) {
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
-                        // If nothing specifically set, assume v2 rules
-                        break;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
-                        // With v2 split into 2 lists, always and undefined, the concept of whether
-                        // or not to ask is irrelevant. Assume the user wants this application to
-                        // open the domain.
-                        return true;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
-                        // Never has the same semantics are before
-                        return false;
-                }
+        if (pkg != null && !DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg,
+                SETTINGS_API_V2)) {
+            switch (mLegacySettings.getUserState(packageName, userId)) {
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+                    // If nothing specifically set, assume v2 rules
+                    break;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+                    return APPROVAL_LEVEL_NONE;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+                    return APPROVAL_LEVEL_LEGACY_ASK;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                    return APPROVAL_LEVEL_LEGACY_ALWAYS;
             }
         }
 
@@ -1203,59 +1385,76 @@
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "pkgState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+            if (userState != null && !userState.isLinkHandlingAllowed()) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, false,
+                            "link handling not allowed");
+                }
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            // The instant app branch must be run after the link handling check,
+            // since that should also disable instant apps if toggled
+            if (pkg != null) {
+                // To allow an instant app to immediately open domains after being installed by the
+                // user, auto approve them for any declared autoVerify domains.
+                if (pkgSetting.getInstantApp(userId)
+                        && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+                    return APPROVAL_LEVEL_INSTANT_APP;
+                }
             }
 
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            // Check if the exact host matches
+            Integer state = stateMap.get(host);
+            if (state != null && DomainVerificationManager.isStateVerified(state)) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, true,
+                            "host verified exactly");
+                }
+                return APPROVAL_LEVEL_VERIFIED;
+            }
 
-            // Only allow autoVerify approval if the user hasn't disabled it
-            if (userState == null || !userState.isDisallowLinkHandling()) {
-                // Check if the exact host matches
-                Integer state = stateMap.get(host);
-                if (state != null && DomainVerificationManager.isStateVerified(state)) {
-                    if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true, "host verified exactly");
-                    }
-                    return true;
+            // Otherwise see if the host matches a verified domain by wildcard
+            int stateMapSize = stateMap.size();
+            for (int index = 0; index < stateMapSize; index++) {
+                if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+                    continue;
                 }
 
-                // Otherwise see if the host matches a verified domain by wildcard
-                int stateMapSize = stateMap.size();
-                for (int index = 0; index < stateMapSize; index++) {
-                    if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
-                        continue;
+                String domain = stateMap.keyAt(index);
+                if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+                    if (DEBUG_APPROVAL) {
+                        debugApproval(packageName, debugObject, userId, true,
+                                "host verified by wildcard");
                     }
-
-                    String domain = stateMap.keyAt(index);
-                    if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
-                        if (DEBUG_APPROVAL) {
-                            debugApproval(packageName, intent, userId, true,
-                                    "host verified by wildcard");
-                        }
-                        return true;
-                    }
+                    return APPROVAL_LEVEL_VERIFIED;
                 }
             }
 
             // Check user state if available
             if (userState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "userState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "userState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
             }
 
             // See if the user has approved the exact host
             ArraySet<String> enabledHosts = userState.getEnabledHosts();
             if (enabledHosts.contains(host)) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, true,
+                    debugApproval(packageName, debugObject, userId, true,
                             "host enabled by user exactly");
                 }
-                return true;
+                return APPROVAL_LEVEL_SELECTION;
             }
 
             // See if the host matches a user selection by wildcard
@@ -1264,24 +1463,85 @@
                 String domain = enabledHosts.valueAt(index);
                 if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
                     if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true,
+                        debugApproval(packageName, debugObject, userId, true,
                                 "host enabled by user through wildcard");
                     }
-                    return true;
+                    return APPROVAL_LEVEL_SELECTION;
                 }
             }
 
             if (DEBUG_APPROVAL) {
-                debugApproval(packageName, intent, userId, false, "not approved");
+                debugApproval(packageName, debugObject, userId, false, "not approved");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
     }
 
-    private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+    /**
+     * @return the filtered list paired with the corresponding approval level
+     */
+    @NonNull
+    private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
+            @UserIdInt int userId, int minimumApproval,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = minimumApproval;
+        List<String> approvedPackages = emptyList();
+
+        synchronized (mLock) {
+            final int size = mAttachedPkgStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                String packageName = pkgState.getPackageName();
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+
+                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+                if (level < minimumApproval) {
+                    continue;
+                }
+
+                if (level > highestApproval) {
+                    approvedPackages.clear();
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                    highestApproval = level;
+                } else if (level == highestApproval) {
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                }
+            }
+        }
+
+        if (approvedPackages.isEmpty()) {
+            return Pair.create(approvedPackages, APPROVAL_LEVEL_NONE);
+        }
+
+        List<String> filteredPackages = new ArrayList<>();
+        long latestInstall = Long.MIN_VALUE;
+        final int approvedSize = approvedPackages.size();
+        for (int index = 0; index < approvedSize; index++) {
+            String packageName = approvedPackages.get(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                continue;
+            }
+            long installTime = pkgSetting.getFirstInstallTime();
+            if (installTime > latestInstall) {
+                latestInstall = installTime;
+                filteredPackages.clear();
+                filteredPackages.add(packageName);
+            } else if (installTime == latestInstall) {
+                filteredPackages.add(packageName);
+            }
+        }
+
+        return Pair.create(filteredPackages, highestApproval);
+    }
+
+    private void debugApproval(@NonNull String packageName, @NonNull Object debugObject,
             @UserIdInt int userId, boolean approved, @NonNull String reason) {
         String approvalString = approved ? "approved" : "denied";
-        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
-                + " for user " + userId + ": " + reason);
+        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+                + debugObject + " for user " + userId + ": " + reason);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index 073967e..a8e937c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -23,7 +23,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -229,14 +228,14 @@
             DomainVerificationUserState oldUserState =
                     oldSelectionStates.get(UserHandle.USER_SYSTEM);
 
-            boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+            boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
             if (oldUserState == null) {
                 oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
-                        newEnabledHosts, disallowLinkHandling);
+                        newEnabledHosts, linkHandlingAllowed);
                 oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
             } else {
                 oldUserState.addHosts(newEnabledHosts)
-                        .setDisallowLinkHandling(disallowLinkHandling);
+                        .setLinkHandlingAllowed(linkHandlingAllowed);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
index 8e82608..2246864 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -25,10 +25,10 @@
 import java.util.Set;
 
 /**
- * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
- * that domain when a web URL Intent is sent ft.
+ * Tracks which domains have been explicitly enabled by the user, allowing it to open that domain
+ * when a web URL Intent is sent and the application is the highest priority for that domain.
  */
-@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
 public class DomainVerificationUserState {
 
     @UserIdInt
@@ -38,8 +38,10 @@
     @NonNull
     private final ArraySet<String> mEnabledHosts;
 
-    /** Whether to disallow this package from automatically opening links by auto verification. */
-    private boolean mDisallowLinkHandling;
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
+    private boolean mLinkHandlingAllowed = true;
 
     public DomainVerificationUserState(@UserIdInt int userId) {
         mUserId = userId;
@@ -67,13 +69,15 @@
     }
 
 
+
     // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
+    // /verify/domain/models/DomainVerificationUserState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -85,19 +89,21 @@
      *
      * @param enabledHosts
      *   List of domains which have been enabled by the user. *
+     * @param linkHandlingAllowed
+     *   Whether to allow this package to automatically open links by auto verification.
      */
     @DataClass.Generated.Member
     public DomainVerificationUserState(
             @UserIdInt int userId,
             @NonNull ArraySet<String> enabledHosts,
-            boolean disallowLinkHandling) {
+            boolean linkHandlingAllowed) {
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
                 UserIdInt.class, null, mUserId);
         this.mEnabledHosts = enabledHosts;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mEnabledHosts);
-        this.mDisallowLinkHandling = disallowLinkHandling;
+        this.mLinkHandlingAllowed = linkHandlingAllowed;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -115,14 +121,20 @@
         return mEnabledHosts;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public boolean isDisallowLinkHandling() {
-        return mDisallowLinkHandling;
+    public boolean isLinkHandlingAllowed() {
+        return mLinkHandlingAllowed;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
-        mDisallowLinkHandling = value;
+    public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+        mLinkHandlingAllowed = value;
         return this;
     }
 
@@ -135,7 +147,7 @@
         return "DomainVerificationUserState { " +
                 "userId = " + mUserId + ", " +
                 "enabledHosts = " + mEnabledHosts + ", " +
-                "disallowLinkHandling = " + mDisallowLinkHandling +
+                "linkHandlingAllowed = " + mLinkHandlingAllowed +
         " }";
     }
 
@@ -154,7 +166,7 @@
         return true
                 && mUserId == that.mUserId
                 && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
-                && mDisallowLinkHandling == that.mDisallowLinkHandling;
+                && mLinkHandlingAllowed == that.mLinkHandlingAllowed;
     }
 
     @Override
@@ -166,15 +178,15 @@
         int _hash = 1;
         _hash = 31 * _hash + mUserId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
-        _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+        _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1608234273324L,
+            time = 1612894390039L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
-            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mDisallowLinkHandling\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mLinkHandlingAllowed\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/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/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/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 37d13fb..12590eb 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
@@ -37,10 +38,12 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgent.ValidationStatus;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
 import android.net.annotations.PolicyDirection;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -58,6 +61,9 @@
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -65,6 +71,7 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -120,6 +127,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 +144,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 +166,15 @@
     private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
-    private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int TEARDOWN_TIMEOUT_SECONDS = 5;
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int SAFEMODE_TIMEOUT_SECONDS = 30;
+
     private interface EventInfo {}
 
     /**
@@ -385,6 +417,23 @@
     // TODO(b/178426520): implement handling of this event
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
 
+    /**
+     * Sent when this VcnGatewayConnection has entered Safemode.
+     *
+     * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+     * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
+     *
+     * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
+     * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+     * its VcnGatewayConnectin(s).
+     *
+     * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
+     * validated yet), and RetryTimeoutState.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     */
+    private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
     final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -419,6 +468,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 +545,13 @@
      * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
      * otherwise.
      */
-    private NetworkAgent mNetworkAgent;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    NetworkAgent mNetworkAgent;
+
+    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
+    @Nullable private WakeupMessage mDisconnectRequestAlarm;
+    @Nullable private WakeupMessage mRetryTimeoutAlarm;
+    @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
 
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
@@ -518,6 +588,9 @@
 
         mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
 
+        mWakeLock =
+                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
                         mVcnContext,
@@ -544,7 +617,7 @@
      * <p>Once torn down, this VcnTunnel CANNOT be started again.
      */
     public void teardownAsynchronously() {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_DISCONNECT_REQUESTED,
                 TOKEN_ALL,
                 new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
@@ -560,6 +633,13 @@
             mTunnelIface.close();
         }
 
+        releaseWakeLock();
+
+        cancelTeardownTimeoutAlarm();
+        cancelDisconnectRequestAlarm();
+        cancelRetryTimeoutAlarm();
+        cancelSafemodeAlarm();
+
         mUnderlyingNetworkTracker.teardown();
     }
 
@@ -576,79 +656,324 @@
         mLastSnapshot = snapshot;
         mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
 
-        sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
     private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
         @Override
         public void onSelectedUnderlyingNetworkChanged(
                 @Nullable UnderlyingNetworkRecord underlying) {
+            // TODO(b/180132994): explore safely removing this Thread check
+            mVcnContext.ensureRunningOnLooperThread();
+
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
             // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
             // timeout.
             if (underlying == null) {
-                sendMessageDelayed(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST),
-                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
-            } else if (getHandler() != null) {
-                // Cancel any existing disconnect due to loss of underlying network
-                // getHandler() can return null if the state machine has already quit. Since this is
-                // called from other classes, this condition must be verified.
-
-                getHandler()
-                        .removeEqualMessages(
-                                EVENT_DISCONNECT_REQUESTED,
-                                new EventDisconnectRequestedInfo(
-                                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+                setDisconnectRequestAlarm();
+            } else {
+                // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
+                // and cancel any queued EVENT_DISCONNECT_REQUEST messages
+                cancelDisconnectRequestAlarm();
             }
 
-            sendMessage(
+            sendMessageAndAcquireWakeLock(
                     EVENT_UNDERLYING_NETWORK_CHANGED,
                     TOKEN_ALL,
                     new EventUnderlyingNetworkChangedInfo(underlying));
         }
     }
 
-    private void sendMessage(int what, int token, EventInfo data) {
+    private void acquireWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (mIsRunning) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mWakeLock.release();
+    }
+
+    /**
+     * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
+     * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
+     * no more messags left to process in the Handler queue (at which point the WakeLock can be
+     * released until more messages must be processed).
+     */
+    private void maybeReleaseWakeLock() {
+        final Handler handler = getHandler();
+        if (handler == null || !handler.hasMessagesOrCallbacks()) {
+            releaseWakeLock();
+        }
+    }
+
+    @Override
+    public void sendMessage(int what) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what);
+    }
+
+    @Override
+    public void sendMessage(int what, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, obj);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2, obj);
+    }
+
+    @Override
+    public void sendMessage(Message msg) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(msg);
+    }
+
+    // TODO(b/180146061): also override and Log.wtf() other Message handling methods
+    // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
+    // removeDeferredMessages
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token) {
+        acquireWakeLock();
+        super.sendMessage(what, token);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, ARG_NOT_PRESENT, data);
     }
 
-    private void sendMessage(int what, int token, int arg2, EventInfo data) {
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, arg2, data);
     }
 
-    private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout);
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(Message msg) {
+        acquireWakeLock();
+        super.sendMessage(msg);
     }
 
-    private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, arg2, data, timeout);
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     */
+    private void removeEqualMessages(int what) {
+        removeEqualMessages(what, null /* obj */);
+    }
+
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
+     *     should be removed
+     */
+    private void removeEqualMessages(int what, @Nullable Object obj) {
+        final Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeEqualMessages(what, obj);
+        }
+
+        maybeReleaseWakeLock();
+    }
+
+    private WakeupMessage createScheduledAlarm(
+            @NonNull String cmdName, Message delayedMessage, long delay) {
+        // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
+        // at the scheduled time. dispatchMessage() immediately executes and there may be queued
+        // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
+        // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
+        // guarantees the device will stay awake).
+        final WakeupMessage alarm =
+                mDeps.newWakeupMessage(
+                        mVcnContext,
+                        getHandler(),
+                        cmdName,
+                        () -> sendMessageAndAcquireWakeLock(delayedMessage));
+        alarm.schedule(mDeps.getElapsedRealTime() + delay);
+        return alarm;
+    }
+
+    private void setTeardownTimeoutAlarm() {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mTeardownTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
+        mTeardownTimeoutAlarm =
+                createScheduledAlarm(
+                        TEARDOWN_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+    }
+
+    private void cancelTeardownTimeoutAlarm() {
+        if (mTeardownTimeoutAlarm != null) {
+            mTeardownTimeoutAlarm.cancel();
+            mTeardownTimeoutAlarm = null;
+        }
+
+        // Cancel any existing teardown timeouts
+        removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
+    }
+
+    private void setDisconnectRequestAlarm() {
+        // Only schedule a NEW alarm if none is already set.
+        if (mDisconnectRequestAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage =
+                obtainMessage(
+                        EVENT_DISCONNECT_REQUESTED,
+                        TOKEN_ALL,
+                        0 /* arg2 */,
+                        new EventDisconnectRequestedInfo(
+                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+        mDisconnectRequestAlarm =
+                createScheduledAlarm(
+                        DISCONNECT_REQUEST_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
+    }
+
+    private void cancelDisconnectRequestAlarm() {
+        if (mDisconnectRequestAlarm != null) {
+            mDisconnectRequestAlarm.cancel();
+            mDisconnectRequestAlarm = null;
+        }
+
+        // Cancel any existing disconnect due to previous loss of underlying network
+        removeEqualMessages(
+                EVENT_DISCONNECT_REQUESTED,
+                new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+    }
+
+    private void setRetryTimeoutAlarm(long delay) {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mRetryTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
+        mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
+    }
+
+    private void cancelRetryTimeoutAlarm() {
+        if (mRetryTimeoutAlarm != null) {
+            mRetryTimeoutAlarm.cancel();
+            mRetryTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setSafemodeAlarm() {
+        // Only schedule a NEW alarm if none is already set.
+        if (mSafemodeTimeoutAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+        mSafemodeTimeoutAlarm =
+                createScheduledAlarm(
+                        SAFEMODE_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    private void cancelSafemodeAlarm() {
+        if (mSafemodeTimeoutAlarm != null) {
+            mSafemodeTimeoutAlarm.cancel();
+            mSafemodeTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
     }
 
     private void sessionLost(int token, @Nullable Exception exception) {
-        sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
     }
 
     private void sessionClosed(int token, @Nullable Exception exception) {
         // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
         // Disconnecting state.
         sessionLost(token, exception);
-        sendMessage(EVENT_SESSION_CLOSED, token);
+        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
     }
 
     private void childTransformCreated(
             int token, @NonNull IpSecTransform transform, int direction) {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_TRANSFORM_CREATED,
                 token,
                 new EventTransformCreatedInfo(direction, transform));
     }
 
     private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
-        sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
     }
 
     private abstract class BaseState extends State {
@@ -658,7 +983,7 @@
                 enterState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -669,22 +994,47 @@
         protected void enterState() throws Exception {}
 
         /**
+         * Returns whether the given token is valid.
+         *
+         * <p>By default, States consider any and all token to be 'valid'.
+         *
+         * <p>States should override this method if they want to restrict message handling to
+         * specific tokens.
+         */
+        protected boolean isValidToken(int token) {
+            return true;
+        }
+
+        /**
          * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
          * builds.
+         *
+         * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
+         * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
+         * ensure that mWakeLock is correctly released.
          */
         @Override
-        public boolean processMessage(Message msg) {
+        public final boolean processMessage(Message msg) {
+            final int token = msg.arg1;
+            if (!isValidToken(token)) {
+                Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
+                return HANDLED;
+            }
+
             try {
                 processStateMsg(msg);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
                                 DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
             }
 
+            // Attempt to release the WakeLock - only possible if the Handler queue is empty
+            maybeReleaseWakeLock();
+
             return HANDLED;
         }
 
@@ -696,7 +1046,7 @@
                 exitState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -774,6 +1124,8 @@
             if (mIkeSession != null || mNetworkAgent != null) {
                 Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
             }
+
+            cancelSafemodeAlarm();
         }
 
         @Override
@@ -797,27 +1149,16 @@
                     break;
             }
         }
+
+        @Override
+        protected void exitState() {
+            // Safe to blindly set up, as it is cancelled and cleared on entering this state
+            setSafemodeAlarm();
+        }
     }
 
     private abstract class ActiveBaseState extends BaseState {
-        /**
-         * Handles all incoming messages, discarding messages for previous networks.
-         *
-         * <p>States that handle mobility events may need to override this method to receive
-         * messages for all underlying networks.
-         */
         @Override
-        public boolean processMessage(Message msg) {
-            final int token = msg.arg1;
-            // Only process if a valid token is presented.
-            if (isValidToken(token)) {
-                return super.processMessage(msg);
-            }
-
-            Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
-            return HANDLED;
-        }
-
         protected boolean isValidToken(int token) {
             return (token == TOKEN_ALL || token == mCurrentToken);
         }
@@ -848,7 +1189,7 @@
         protected void enterState() throws Exception {
             if (mIkeSession == null) {
                 Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
-                sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+                sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
                 return;
             }
 
@@ -860,10 +1201,9 @@
             }
 
             mIkeSession.close();
-            sendMessageDelayed(
-                    EVENT_TEARDOWN_TIMEOUT_EXPIRED,
-                    mCurrentToken,
-                    TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+
+            // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+            setTeardownTimeoutAlarm();
         }
 
         @Override
@@ -905,6 +1245,10 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -914,6 +1258,8 @@
         @Override
         protected void exitState() throws Exception {
             mSkipRetryTimeout = false;
+
+            cancelTeardownTimeoutAlarm();
         }
     }
 
@@ -985,6 +1331,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1028,6 +1378,14 @@
                         public void unwanted() {
                             teardownAsynchronously();
                         }
+
+                        @Override
+                        public void onValidationStatus(
+                                @ValidationStatus int status, @Nullable Uri redirectUri) {
+                            if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+                                clearFailedAttemptCounterAndSafeModeAlarm();
+                            }
+                        }
                     };
 
             agent.register();
@@ -1036,6 +1394,14 @@
             return agent;
         }
 
+        protected void clearFailedAttemptCounterAndSafeModeAlarm() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            // Validated connection, clear failed attempt counter
+            mFailedAttempts = 0;
+            cancelSafemodeAlarm();
+        }
+
         protected void applyTransform(
                 int token,
                 @NonNull IpSecTunnelInterface tunnelIface,
@@ -1043,7 +1409,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 +1481,6 @@
                     teardownAsynchronously();
                 }
             }
-
-            // Successful connection, clear failed attempt counter
-            mFailedAttempts = 0;
         }
 
         @Override
@@ -1154,6 +1517,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1173,6 +1540,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 +1564,19 @@
                 mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
             } else {
                 updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
+
+                // mNetworkAgent not null, so the VCN Network has already been established. Clear
+                // the failed attempt counter and safe mode alarm since this transition is complete.
+                clearFailedAttemptCounterAndSafeModeAlarm();
             }
         }
+
+        @Override
+        protected void exitState() {
+            // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
+            // while in ConnectedState (which cancels the previous alarm)
+            setSafemodeAlarm();
+        }
     }
 
     /**
@@ -1215,8 +1594,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 +1608,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 +1618,26 @@
 
                     // Fallthrough
                 case EVENT_RETRY_TIMEOUT_EXPIRED:
-                    removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
                     transitionTo(mConnectingState);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
             }
         }
 
+        @Override
+        public void exitState() {
+            cancelRetryTimeoutAlarm();
+        }
+
         private long getNextRetryIntervalsMs() {
             final int retryDelayIndex = mFailedAttempts - 1;
             final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs();
@@ -1425,6 +1809,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 +1919,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 +2018,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/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 260d79a..1750854 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -352,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;
@@ -366,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);
                     }
@@ -491,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);
             }
@@ -595,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;
         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5697564..370d921 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -31,6 +31,7 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ILocalWallpaperColorConsumer;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
@@ -59,6 +60,7 @@
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -85,7 +87,10 @@
 import android.service.wallpaper.WallpaperService;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -105,7 +110,6 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.WindowManagerInternal;
@@ -136,6 +140,8 @@
     private static final String TAG = "WallpaperManagerService";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LIVE = true;
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     public static class Lifecycle extends SystemService {
         private IWallpaperManagerService mService;
@@ -866,6 +872,12 @@
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
     private int mCurrentUserId = UserHandle.USER_NULL;
     private boolean mInAmbientMode;
+    private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
+            new ArrayMap<>();
+    private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
+            mLocalColorAreaCallbacks = new ArrayMap<>();
+    private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
+    private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
 
     static class WallpaperData {
 
@@ -1276,6 +1288,32 @@
         }
 
         @Override
+        public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
+                int displayId) {
+            forEachDisplayConnector(displayConnector -> {
+                if (displayConnector.mDisplayId == displayId) {
+                    RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
+                    ArrayMap<IBinder, Integer> callbackDisplayIds;
+                    synchronized (mLock) {
+                        callbacks = mLocalColorAreaCallbacks.get(area);
+                        callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
+                    }
+                    if (callbacks == null) return;
+                    callbacks.broadcast(c -> {
+                        try {
+                            int targetDisplayId =
+                                    callbackDisplayIds.get(c.asBinder());
+                            if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    });
+                }
+            });
+        }
+
+
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 Slog.w(TAG, "Wallpaper service gone: " + name);
@@ -1437,6 +1475,15 @@
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to request wallpaper colors", e);
                 }
+
+                ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
+                if (areas != null && areas.size() != 0) {
+                    try {
+                        connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to register local colors areas", e);
+                    }
+                }
             }
         }
 
@@ -2340,6 +2387,115 @@
         return true;
     }
 
+    private IWallpaperEngine getEngine(int which, int userId, int displayId) {
+        WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
+        if (wallpaperData == null) return null;
+        IWallpaperEngine engine = null;
+        synchronized (mLock) {
+            for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
+                int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+                int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+                if (id != displayId && currentWhich != which) continue;
+                engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
+                break;
+            }
+        }
+        return engine;
+    }
+
+    @Override
+    public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
+            @NonNull List<RectF> regions, int which, int userId, int displayId)
+            throws RemoteException {
+        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+        }
+        IWallpaperEngine engine = getEngine(which, userId, displayId);
+        if (engine == null) return;
+        ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
+        synchronized (mLock) {
+            ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
+            if (areas == null) areas = new ArraySet<>(regions.size());
+            areas.addAll(regions);
+            mLocalColorCallbackAreas.put(callback.asBinder(), areas);
+        }
+        for (int i = 0; i < regions.size(); i++) {
+            if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
+                continue;
+            }
+            RemoteCallbackList callbacks;
+            synchronized (mLock) {
+                callbacks = mLocalColorAreaCallbacks.get(
+                        regions.get(i));
+                if (callbacks == null) {
+                    callbacks = new RemoteCallbackList();
+                    mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
+                }
+                mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
+                ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+                if (displayAreas == null) {
+                    displayAreas = new ArraySet<>(1);
+                    mLocalColorDisplayIdAreas.put(displayId, displayAreas);
+                }
+                displayAreas.add(regions.get(i));
+            }
+            validAreas.add(regions.get(i));
+            callbacks.register(callback);
+        }
+        engine.addLocalColorsAreas(validAreas);
+    }
+
+    @Override
+    public void removeOnLocalColorsChangedListener(
+            @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
+            int displayId) throws RemoteException {
+        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+        }
+        final UserHandle callingUser = Binder.getCallingUserHandle();
+        if (callingUser.getIdentifier() != userId) {
+            throw new SecurityException("calling user id does not match");
+        }
+        final long identity = Binder.clearCallingIdentity();
+        ArrayList<RectF> removeAreas = new ArrayList<>();
+        ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
+                callbacksToRemove = new ArrayList<>();
+        try {
+            synchronized (mLock) {
+                ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
+                mLocalColorCallbackDisplayId.remove(callback.asBinder());
+                if (areas == null) areas = new ArraySet<>();
+                for (RectF area : areas) {
+                    RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
+                    if (callbacks == null) continue;
+                    callbacksToRemove.add(new Pair<>(callbacks, callback));
+                    if (callbacks.getRegisteredCallbackCount() == 0) {
+                        mLocalColorAreaCallbacks.remove(area);
+                        removeAreas.add(area);
+                    }
+                    ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+                    if (displayAreas != null) {
+                        displayAreas.remove(area);
+                    }
+                }
+            }
+            for (int i = 0; i < callbacksToRemove.size(); i++) {
+                Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
+                        pair = callbacksToRemove.get(i);
+                pair.first.unregister(pair.second);
+            }
+        } catch (Exception e) {
+            // ignore any exception
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (removeAreas.size() == 0) return;
+        IWallpaperEngine engine = getEngine(which, userId, displayId);
+        if (engine == null) return;
+        engine.removeLocalColorsAreas(removeAreas);
+    }
+
     @Override
     public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
             throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f440e56..9bf6df4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5378,6 +5378,7 @@
             }
         }
 
+        mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
         mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8bd4dfd..66d5c77 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5855,12 +5855,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
@@ -5883,9 +5879,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();
             }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 286d31e..c0bce6b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -674,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/jni/Android.bp b/services/core/jni/Android.bp
index 3012d73..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",
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/onload.cpp b/services/core/jni/onload.cpp
index 24221e7..1815f0c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,6 +41,7 @@
 int register_android_server_vibrator_VibratorController(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);
@@ -91,6 +92,7 @@
     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);
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
index e27e1b8..1406dbb 100644
--- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -27,6 +27,13 @@
         <xs:attribute type="xs:boolean" name="enabled" use="required" />
     </xs:complexType>
 
+    <xs:complexType name="raw-override-value">
+        <xs:attribute type="xs:string" name="packageName" use="required" />
+        <xs:attribute type="xs:long" name="minVersionCode" />
+        <xs:attribute type="xs:long" name="maxVersionCode" />
+        <xs:attribute type="xs:boolean" name="enabled" use="required" />
+    </xs:complexType>
+
     <xs:complexType name="change-overrides">
         <xs:attribute type="xs:long" name="changeId" use="required"/>
         <xs:element name="validated">
@@ -43,6 +50,13 @@
                 </xs:sequence>
             </xs:complexType>
         </xs:element>
+        <xs:element name="raw">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
     </xs:complexType>
 
     <xs:element name="overrides">
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
index 08b8207..a5ccffc 100644
--- a/services/core/xsd/platform-compat/overrides/schema/current.txt
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -5,9 +5,11 @@
     ctor public ChangeOverrides();
     method public long getChangeId();
     method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+    method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw();
     method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
     method public void setChangeId(long);
     method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+    method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw);
     method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
   }
 
@@ -16,6 +18,11 @@
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
   }
 
+  public static class ChangeOverrides.Raw {
+    ctor public ChangeOverrides.Raw();
+    method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue();
+  }
+
   public static class ChangeOverrides.Validated {
     ctor public ChangeOverrides.Validated();
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
@@ -34,6 +41,18 @@
     method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
   }
 
+  public class RawOverrideValue {
+    ctor public RawOverrideValue();
+    method public boolean getEnabled();
+    method public long getMaxVersionCode();
+    method public long getMinVersionCode();
+    method public String getPackageName();
+    method public void setEnabled(boolean);
+    method public void setMaxVersionCode(long);
+    method public void setMinVersionCode(long);
+    method public void setPackageName(String);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 59b7367..e76597d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -138,6 +138,7 @@
     private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
     private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
             "admin-can-grant-sensors-permissions";
+    private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -281,6 +282,9 @@
     public String mEnrollmentSpecificId;
     public boolean mAdminCanGrantSensorsPermissions;
 
+    private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
+    boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
+
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
         this.info = info;
         this.isParent = isParent;
@@ -548,6 +552,9 @@
         }
         writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
                 mAdminCanGrantSensorsPermissions);
+        if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
+        }
     }
 
     void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -800,6 +807,9 @@
             } else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) {
                 mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE,
                         false);
+            } else if (TAG_USB_DATA_SIGNALING.equals(tag)) {
+                mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
+                        USB_DATA_SIGNALING_ENABLED_DEFAULT);
             } else {
                 Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1154,5 +1164,8 @@
 
         pw.print("mAdminCanGrantSensorsPermissions");
         pw.println(mAdminCanGrantSensorsPermissions);
+
+        pw.print("mUsbDataSignaling=");
+        pw.println(mUsbDataSignalingEnabled);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9a692be7..604d1d6 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;
@@ -421,10 +422,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[] {
@@ -781,8 +784,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 +1359,10 @@
             return mContext.getSystemService(WifiManager.class);
         }
 
+        UsbManager getUsbManager() {
+            return mContext.getSystemService(UsbManager.class);
+        }
+
         @SuppressWarnings("AndroidFrameworkBinderIdentity")
         long binderClearCallingIdentity() {
             return Binder.clearCallingIdentity();
@@ -2928,6 +2934,7 @@
 
             revertTransferOwnershipIfNecessaryLocked();
         }
+        updateUsbDataSignal();
     }
 
     private void revertTransferOwnershipIfNecessaryLocked() {
@@ -5879,10 +5886,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));
         }
@@ -7527,6 +7534,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));
@@ -8351,6 +8372,7 @@
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
         applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        setNetworkLoggingActiveInternal(false);
     }
 
     @Override
@@ -14131,7 +14153,9 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14139,11 +14163,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);
@@ -14161,7 +14185,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 +14211,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 +14250,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 +14274,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 +14303,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 +14315,8 @@
     }
 
     private boolean isNetworkLoggingEnabledInternalLocked() {
-        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+        ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
     }
 
     /*
@@ -14278,9 +14333,13 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
-        checkAllUsersAreAffiliatedWithDevice();
+        if (mOwners.hasDeviceOwner()) {
+            checkAllUsersAreAffiliatedWithDevice();
+        }
 
         synchronized (getLockObject()) {
             if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) {
@@ -14293,34 +14352,35 @@
                     .write();
 
             final long currentTime = System.currentTimeMillis();
-            DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+            DevicePolicyData policyData = getUserData(caller.getUserId());
             if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
                 policyData.mLastNetworkLogsRetrievalTime = currentTime;
-                saveSettingsLocked(UserHandle.USER_SYSTEM);
+                saveSettingsLocked(caller.getUserId());
             }
             return mNetworkLogger.retrieveLogs(batchToken);
         }
     }
 
     private void sendNetworkLoggingNotificationLocked() {
-        final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
+        ensureLocked();
+        final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) {
             return;
         }
-        if (deviceOwner.numNetworkLoggingNotifications >=
-                ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+        if (activeAdmin.numNetworkLoggingNotifications
+                >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
             return;
         }
         final long now = System.currentTimeMillis();
-        if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+        if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
             return;
         }
-        deviceOwner.numNetworkLoggingNotifications++;
-        if (deviceOwner.numNetworkLoggingNotifications
+        activeAdmin.numNetworkLoggingNotifications++;
+        if (activeAdmin.numNetworkLoggingNotifications
                 >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
         } else {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = now;
         }
         final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
         final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
@@ -14340,7 +14400,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 +14464,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
@@ -16567,4 +16631,78 @@
 
         return mPolicyCache.canAdminGrantSensorsPermissionsForUser(userId);
     }
+
+    @Override
+    public void setUsbDataSignalingEnabled(String packageName, boolean enabled) {
+        Objects.requireNonNull(packageName, "Admin package name must be provided");
+        final CallerIdentity caller = getCallerIdentity(packageName);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "USB data signaling can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+        Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+                "USB data signaling cannot be disabled.");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            if (admin.mUsbDataSignalingEnabled != enabled) {
+                admin.mUsbDataSignalingEnabled = enabled;
+                saveSettingsLocked(caller.getUserId());
+                updateUsbDataSignal();
+            }
+        }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
+                .setAdmin(packageName)
+                .setBoolean(enabled)
+                .write();
+    }
+
+    private void updateUsbDataSignal() {
+        if (!canUsbDataSignalingBeDisabled()) {
+            return;
+        }
+        final boolean usbEnabled;
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            usbEnabled = admin != null && admin.mUsbDataSignalingEnabled;
+        }
+        if (!mInjector.binderWithCleanCallingIdentity(
+                () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) {
+            Slog.w(LOG_TAG, "Failed to set usb data signaling state");
+        }
+    }
+
+    @Override
+    public boolean isUsbDataSignalingEnabled(String packageName) {
+        final CallerIdentity caller = getCallerIdentity(packageName);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "USB data signaling can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            return admin.mUsbDataSignalingEnabled;
+        }
+    }
+
+    @Override
+    public boolean isUsbDataSignalingEnabledForUser(int userId) {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isSystemUid(caller));
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            return admin != null && admin.mUsbDataSignalingEnabled;
+        }
+    }
+
+    @Override
+    public boolean canUsbDataSignalingBeDisabled() {
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/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 bd20464..dd2dd81 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;
@@ -253,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 =
@@ -321,6 +326,8 @@
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
             "com.android.server.systemcaptions.SystemCaptionsManagerService";
+    private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
+            "com.android.server.texttospeech.TextToSpeechManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
     private static final String IOT_SERVICE_CLASS =
@@ -1708,6 +1715,7 @@
             startAttentionService(context, t);
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
+            startTextToSpeechManagerService(context, t);
 
             // System Speech Recognition Service
             if (deviceHasConfigString(context,
@@ -2392,6 +2400,10 @@
             t.traceBegin("StartPeopleService");
             mSystemServiceManager.startService(PeopleService.class);
             t.traceEnd();
+
+            t.traceBegin("StartMediaMetricsManager");
+            mSystemServiceManager.startService(MediaMetricsManagerService.class);
+            t.traceEnd();
         }
 
         if (!isWatch) {
@@ -2447,6 +2459,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);
@@ -2903,6 +2921,13 @@
         t.traceEnd();
     }
 
+    private void startTextToSpeechManagerService(@NonNull Context context,
+            @NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("StartTextToSpeechManagerService");
+        mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+        t.traceEnd();
+    }
+
     private void startContentCaptureService(@NonNull Context context,
             @NonNull TimingsTraceAndSlog t) {
         // First check if it was explicitly enabled by DeviceConfig
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/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/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..5bb65ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -89,6 +89,7 @@
 import android.content.pm.StringParceledListSlice;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
+import android.hardware.usb.UsbManager;
 import android.net.Uri;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -4329,6 +4330,68 @@
     }
 
     @Test
+    public void testSetNetworkLoggingEnabled_asPo() throws Exception {
+        final int managedProfileUserId = CALLER_USER_HANDLE;
+        final int managedProfileAdminUid =
+                UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.packageName = admin1.getPackageName();
+        addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
+    public void testSetNetworkLoggingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+        // Setup profile owner on organization-owned device
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mContext.packageName = admin1.getPackageName();
+        mContext.applicationInfo = new ApplicationInfo();
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
     public void testGetBindDeviceAdminTargetUsers() throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
 
@@ -6940,6 +7003,82 @@
                         DevicePolicyManager.PASSWORD_QUALITY_COMPLEX));
     }
 
+    @Test
+    public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+        assertThrows(SecurityException.class,
+                () -> dpm.setUsbDataSignalingEnabled(true));
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+        dpm.setUsbDataSignalingEnabled(false);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+    }
+
+    @Test
+    public void testIsUsbDataSignalingEnabledForUser_systemUser() throws Exception {
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+        setDeviceOwner();
+        dpm.setUsbDataSignalingEnabled(false);
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+
+        assertThat(dpm.isUsbDataSignalingEnabledForUser(UserHandle.myUserId())).isFalse();
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+        dpm.setUsbDataSignalingEnabled(false);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+    }
+
+    @Test
+    public void testCanUsbDataSignalingBeDisabled_canBeDisabled() throws Exception {
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.canUsbDataSignalingBeDisabled()).isTrue();
+    }
+
+    @Test
+    public void testCanUsbDataSignalingBeDisabled_cannotBeDisabled() throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_2);
+
+        assertThat(dpm.canUsbDataSignalingBeDisabled()).isFalse();
+        assertThrows(IllegalStateException.class,
+                () -> dpm.setUsbDataSignalingEnabled(true));
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_noChangeToActiveAdmin()
+            throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+        boolean enabled = dpm.isUsbDataSignalingEnabled();
+
+        dpm.setUsbDataSignalingEnabled(true);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isEqualTo(enabled);
+    }
+
     private void setUserUnlocked(int userHandle, boolean unlocked) {
         when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 944ef8d..f6dee38 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -45,6 +45,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.database.Cursor;
+import android.hardware.usb.UsbManager;
 import android.media.IAudioService;
 import android.net.IIpConnectivityMetrics;
 import android.net.Uri;
@@ -119,6 +120,7 @@
     public final CrossProfileApps crossProfileApps;
     public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
     public final AppOpsManager appOpsManager;
+    public final UsbManager usbManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -163,6 +165,7 @@
         crossProfileApps = mock(CrossProfileApps.class);
         persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
         appOpsManager = mock(AppOpsManager.class);
+        usbManager = mock(UsbManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 7506dd4..743b25f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -125,7 +125,7 @@
     private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
         // GIVEN a handler with events
         NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
-                mDpmTestable, startingId);
+                mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE);
         // GIVEN network events are sent to the handler.
         for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
             ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
diff --git a/services/tests/servicestests/src/com/android/server/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..fcbd897 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;
@@ -99,7 +96,7 @@
 
     @Override
     public int nativeGetVersion() {
-        return 0;
+        return HdmiControlManager.HDMI_CEC_VERSION_2_0;
     }
 
     @Override
@@ -139,20 +136,15 @@
         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 +152,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..5342486 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_INVALID;
@@ -49,6 +50,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
@@ -1586,4 +1588,46 @@
         assertThat(features.contains(
                 Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)).isFalse();
     }
+
+    @Test
+    public void doesNotSupportRecordTvScreen() {
+        HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress,
+                Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+        mNativeWrapper.onCecMessage(recordTvScreen);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_RECORD_TV_SCREEN,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void shouldHandleUserControlPressedAndReleased() {
+        HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                ADDR_TV, mPlaybackLogicalAddress,
+                HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                ADDR_TV, mPlaybackLogicalAddress);
+
+        mNativeWrapper.onCecMessage(userControlPressed);
+        mTestLooper.dispatchAll();
+
+        // Move past the follower safety timeout
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(2));
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(userControlReleased);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbortPressed = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_PRESSED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        HdmiCecMessage featureAbortReleased = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_RELEASED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index d5271ef..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,9 +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;
 
@@ -484,4 +486,18 @@
                 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/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/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/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/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/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/texttospeech/Android.bp b/services/texttospeech/Android.bp
new file mode 100644
index 0000000..bacc932
--- /dev/null
+++ b/services/texttospeech/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.texttospeech-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.texttospeech",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.texttospeech-sources"],
+    libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
new file mode 100644
index 0000000..f805904
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.texttospeech;
+
+import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.speech.tts.ITextToSpeechService;
+import android.speech.tts.ITextToSpeechSession;
+import android.speech.tts.ITextToSpeechSessionCallback;
+import android.speech.tts.TextToSpeech;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}.
+ * Creates {@link TtsClient} interface object with direct connection to
+ * {@link android.speech.tts.TextToSpeechService} provider.
+ *
+ * @see ITextToSpeechSession
+ * @see TextToSpeech
+ */
+final class TextToSpeechManagerPerUserService extends
+        AbstractPerUserSystemService<TextToSpeechManagerPerUserService,
+                TextToSpeechManagerService> {
+
+    private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName();
+
+    TextToSpeechManagerPerUserService(
+            @NonNull TextToSpeechManagerService master,
+            @NonNull Object lock, @UserIdInt int userId) {
+        super(master, lock, userId);
+    }
+
+    void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) {
+        TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback);
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    @NonNull
+    protected ServiceInfo newServiceInfoLocked(
+            @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        try {
+            return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+    }
+
+    private static class TextToSpeechSessionConnection extends
+            ServiceConnector.Impl<ITextToSpeechService> {
+
+        private final String mEngine;
+        private final ITextToSpeechSessionCallback mCallback;
+        private final DeathRecipient mUnbindOnDeathHandler;
+
+        static void start(Context context, @UserIdInt int userId, String engine,
+                ITextToSpeechSessionCallback callback) {
+            new TextToSpeechSessionConnection(context, userId, engine, callback).start();
+        }
+
+        private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine,
+                ITextToSpeechSessionCallback callback) {
+            super(context,
+                    new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
+                    Context.BIND_AUTO_CREATE,
+                    userId,
+                    ITextToSpeechService.Stub::asInterface);
+            mEngine = engine;
+            mCallback = callback;
+            mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported");
+        }
+
+        private void start() {
+            Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine);
+
+            connect()
+                    .thenAccept(
+                            serviceBinder -> {
+                                if (serviceBinder != null) {
+                                    Slog.d(TAG,
+                                            "Connected successfully to TTS engine: " + mEngine);
+                                    try {
+                                        mCallback.onConnected(new ITextToSpeechSession.Stub() {
+                                            @Override
+                                            public void disconnect() {
+                                                unbindEngine("client disconnection request");
+                                            }
+                                        }, serviceBinder.asBinder());
+
+                                        mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0);
+                                    } catch (RemoteException ex) {
+                                        Slog.w(TAG, "Error notifying the client on connection", ex);
+
+                                        unbindEngine(
+                                                "failed communicating with the client - process "
+                                                        + "is dead");
+                                    }
+                                } else {
+                                    Slog.w(TAG, "Failed to obtain TTS engine binder");
+                                    runSessionCallbackMethod(
+                                            () -> mCallback.onError("Failed creating TTS session"));
+                                }
+                            })
+                    .exceptionally(ex -> {
+                        Slog.w(TAG, "TTS engine binding error", ex);
+                        runSessionCallbackMethod(
+                                () -> mCallback.onError(
+                                        "Failed creating TTS session: " + ex.getCause()));
+
+                        return null;
+                    });
+        }
+
+        @Override // from ServiceConnector.Impl
+        protected void onServiceConnectionStatusChanged(
+                ITextToSpeechService service, boolean connected) {
+            if (!connected) {
+                Slog.w(TAG, "Disconnected from TTS engine");
+                runSessionCallbackMethod(mCallback::onDisconnected);
+
+                try {
+                    mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0);
+                } catch (NoSuchElementException ex) {
+                    Slog.d(TAG, "The death recipient was not linked.");
+                }
+            }
+        }
+
+        @Override // from ServiceConnector.Impl
+        protected long getAutoDisconnectTimeoutMs() {
+            return PERMANENT_BOUND_TIMEOUT_MS;
+        }
+
+        private void unbindEngine(String reason) {
+            Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason);
+            unbind();
+        }
+    }
+
+    static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) {
+        try {
+            callbackRunnable.runOrThrow();
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed running callback method", ex);
+        }
+    }
+
+    interface ThrowingRunnable {
+        void runOrThrow() throws RemoteException;
+    }
+}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
new file mode 100644
index 0000000..9015563
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.texttospeech;
+
+import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+import android.speech.tts.ITextToSpeechManager;
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+import com.android.server.infra.AbstractMasterSystemService;
+
+
+/**
+ * A service that  allows secured synthesizing of text to speech audio. Upon request creates a
+ * session
+ * that is managed by {@link TextToSpeechManagerPerUserService}.
+ *
+ * @see ITextToSpeechManager
+ */
+public final class TextToSpeechManagerService extends
+        AbstractMasterSystemService<TextToSpeechManagerService,
+                TextToSpeechManagerPerUserService> {
+
+    private static final String TAG = TextToSpeechManagerService.class.getSimpleName();
+
+    public TextToSpeechManagerService(@NonNull Context context) {
+        super(context, /* serviceNameResolver= */ null,
+                /* disallowProperty = */null);
+    }
+
+    @Override // from SystemService
+    public void onStart() {
+        publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE,
+                new TextToSpeechManagerServiceStub());
+    }
+
+    @Override
+    protected TextToSpeechManagerPerUserService newServiceLocked(
+            @UserIdInt int resolvedUserId, boolean disabled) {
+        return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId);
+    }
+
+    private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub {
+        @Override
+        public void createSession(String engine,
+                ITextToSpeechSessionCallback sessionCallback) {
+            synchronized (mLock) {
+                TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
+                        UserHandle.getCallingUserId());
+                if (perUserService != null) {
+                    perUserService.createSessionLocked(engine, sessionCallback);
+                } else {
+                    runSessionCallbackMethod(
+                            () -> sessionCallback.onError("Service is not available for user"));
+                }
+            }
+        }
+    }
+}
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/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 8b34755..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -496,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 67417bd..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
@@ -554,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";
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..2a693eb 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2754,10 +2754,6 @@
 
         NetworkCapabilities filter = new NetworkCapabilities();
         filter.addCapability(capability);
-        // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
-        // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
-        // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
-        filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -4117,7 +4113,6 @@
         handlerThread.start();
         NetworkCapabilities filter = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .addCapability(NET_CAPABILITY_INTERNET);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter);
@@ -6021,7 +6016,6 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkDownstreamBandwidthKbps(10);
         final NetworkCapabilities wifiNc = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
@@ -6030,7 +6024,6 @@
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
                 .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkUpstreamBandwidthKbps(20);
         mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
         mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
@@ -7718,13 +7711,19 @@
             mWiFiNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
             callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
-            reset(mMockNetd);
+            // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
+            //  it.
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+                reset(mMockNetd);
+            }
 
             mCellNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
             callbackWithoutCap.assertNoCallback();
-            verify(mMockNetd).networkClearDefault();
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkClearDefault();
+            }
 
             mCm.unregisterNetworkCallback(callbackWithCap);
             mCm.unregisterNetworkCallback(callbackWithoutCap);
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/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..e715480 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -34,8 +34,10 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.net.LinkProperties;
+import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 
 import androidx.test.filters.SmallTest;
@@ -73,6 +75,11 @@
     }
 
     @Test
+    public void testEnterStateDoesNotCancelSafemodeAlarm() {
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    }
+
+    @Test
     public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
         mGatewayConnection
                 .getUnderlyingNetworkTrackerCallback()
@@ -81,6 +88,7 @@
 
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
         verify(mIkeSession, never()).close();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -120,7 +128,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 +187,41 @@
         for (int cap : mConfig.getAllExposedCapabilities()) {
             assertTrue(nc.hasCapability(cap));
         }
+
+        // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+        // canceled
+        mGatewayConnection.mNetworkAgent.onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
+        verify(mSafemodeTimeoutAlarm).cancel();
     }
 
     @Test
     public void testChildSessionClosedTriggersDisconnect() throws Exception {
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
         getChildSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
     }
 
     @Test
     public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+
+        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d936183..07282c9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -61,6 +61,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).kill();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -73,6 +74,7 @@
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
         verify(mIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -92,6 +94,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -101,5 +104,11 @@
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 16181b6..49ce54d 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..22eab2a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.vcn;
 
-import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import androidx.test.filters.SmallTest;
@@ -28,8 +28,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
-
 /** Tests for VcnGatewayConnection.DisconnectedState */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -40,6 +38,9 @@
 
         mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
 
+        // ensure that mGatewayConnection has an underlying Network before entering
+        // DisconnectingState
+        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2);
         mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
         mTestLooper.dispatchAll();
     }
@@ -49,12 +50,22 @@
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
-        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verify(mMockIkeSession).close();
+        verify(mMockIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutExpired() throws Exception {
-        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        Runnable delayedEvent =
+                verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         verify(mMockIkeSession).kill();
@@ -67,5 +78,11 @@
 
         // Should do nothing; already tearing down.
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 3f2b47c..6c26075 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -29,10 +29,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
+    private long mFirstRetryInterval;
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
+        mFirstRetryInterval = mConfig.getRetryInterval()[0];
+
         mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
         mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState);
         mTestLooper.dispatchAll();
@@ -46,6 +50,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
@@ -56,6 +61,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */);
     }
 
     @Test
@@ -66,13 +72,28 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutElapsingTriggersRetry() throws Exception {
-        mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]);
+        final Runnable delayedEvent =
+                verifyRetryTimeoutAlarmAndGetCallback(
+                        mFirstRetryInterval, false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index bc6bee2..748c792 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -132,10 +132,32 @@
 
     @Test
     public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+        verifyWakeLockSetUp();
+
         final TelephonySubscriptionSnapshot updatedSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
         mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
 
         verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
+        verifyWakeLockAcquired();
+
+        mTestLooper.dispatchAll();
+
+        verifyWakeLockReleased();
+    }
+
+    @Test
+    public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
+
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+        verify(mDisconnectRequestAlarm).cancel();
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index d449eab..ac9ec06 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -20,10 +20,16 @@
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -42,12 +48,16 @@
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 
+import com.android.internal.util.State;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.IpSecService;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
 
 import org.junit.Before;
 import org.mockito.ArgumentCaptor;
@@ -55,6 +65,7 @@
 import java.net.InetAddress;
 import java.util.Collections;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 public class VcnGatewayConnectionTestBase {
     protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
@@ -68,6 +79,7 @@
     protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
     protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
     protected static final int TEST_SUB_ID = 5;
+    protected static final long ELAPSED_REAL_TIME = 123456789L;
     protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
     protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
             new UnderlyingNetworkRecord(
@@ -94,6 +106,11 @@
     @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
     @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull protected final VcnWakeLock mWakeLock;
+    @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
+    @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
+    @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
+    @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
@@ -110,6 +127,11 @@
         mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
         mDeps = mock(VcnGatewayConnection.Dependencies.class);
         mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+        mWakeLock = mock(VcnWakeLock.class);
+        mTeardownTimeoutAlarm = mock(WakeupMessage.class);
+        mDisconnectRequestAlarm = mock(WakeupMessage.class);
+        mRetryTimeoutAlarm = mock(WakeupMessage.class);
+        mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
 
         mIpSecSvc = mock(IpSecService.class);
         setupIpSecManager(mContext, mIpSecSvc);
@@ -125,6 +147,20 @@
         doReturn(mUnderlyingNetworkTracker)
                 .when(mDeps)
                 .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
+        doReturn(mWakeLock)
+                .when(mDeps)
+                .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+
+        setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
+        setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
+        setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
+        setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+
+        doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
+    }
+
+    private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
+        doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
     }
 
     @Before
@@ -166,4 +202,80 @@
         verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
         return (VcnChildSessionCallback) captor.getValue();
     }
+
+    protected void verifyWakeLockSetUp() {
+        verify(mDeps).newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockAcquired() {
+        verify(mWakeLock).acquire();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockReleased() {
+        verify(mWakeLock).release();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    private Runnable verifyWakeupMessageSetUpAndGetCallback(
+            @NonNull String tag,
+            @NonNull WakeupMessage msg,
+            long delayInMillis,
+            boolean expectCanceled) {
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture());
+
+        verify(mDeps, atLeastOnce()).getElapsedRealTime();
+        verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis);
+        verify(msg, expectCanceled ? times(1) : never()).cancel();
+
+        return runnableCaptor.getValue();
+    }
+
+    protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM,
+                mTeardownTimeoutAlarm,
+                TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.DISCONNECT_REQUEST_ALARM,
+                mDisconnectRequestAlarm,
+                TimeUnit.SECONDS.toMillis(
+                        VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyRetryTimeoutAlarmAndGetCallback(
+            long delayInMillis, boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.RETRY_TIMEOUT_ALARM,
+                mRetryTimeoutAlarm,
+                delayInMillis,
+                expectCanceled);
+    }
+
+    protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
+                mSafemodeTimeoutAlarm,
+                TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
+        // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+        // state)
+        final Runnable delayedEvent =
+                verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        delayedEvent.run();
+        mTestLooper.dispatchAll();
+
+        verify(mGatewayStatusCallback).onEnteredSafemode();
+        assertEquals(expectedState, mGatewayConnection.getCurrentState());
+    }
 }