Merge "Reduce ifs lock scope." into sc-dev
diff --git a/Android.bp b/Android.bp
index f47ee20..6a47db1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -669,7 +669,6 @@
         // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
         "gps_debug.conf",
         "icu4j-platform-compat-config",
-        "libcore-platform-compat-config",
         "protolog.conf.json.gz",
         "services-platform-compat-config",
         "documents-ui-compat-config",
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index e08d22c..a3e05dc 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -8,6 +8,14 @@
     method public boolean isSuccess();
   }
 
+  public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
+    ctor public AppSearchBatchResult.Builder();
+    method @NonNull public android.app.appsearch.AppSearchBatchResult<KeyType,ValueType> build();
+    method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setFailure(@NonNull KeyType, int, @Nullable String);
+    method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setResult(@NonNull KeyType, @NonNull android.app.appsearch.AppSearchResult<ValueType>);
+    method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setSuccess(@NonNull KeyType, @Nullable ValueType);
+  }
+
   public class AppSearchManager {
     method public void createGlobalSearchSession(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GlobalSearchSession>>);
     method public void createSearchSession(@NonNull android.app.appsearch.AppSearchManager.SearchContext, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.AppSearchSession>>);
@@ -29,6 +37,8 @@
     method public int getResultCode();
     method @Nullable public ValueType getResultValue();
     method public boolean isSuccess();
+    method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newFailedResult(int, @Nullable String);
+    method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newSuccessfulResult(@Nullable ValueType);
     field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
     field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3
     field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7
@@ -36,13 +46,14 @@
     field public static final int RESULT_NOT_FOUND = 6; // 0x6
     field public static final int RESULT_OK = 0; // 0x0
     field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5
+    field public static final int RESULT_SECURITY_ERROR = 8; // 0x8
     field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1
   }
 
   public final class AppSearchSchema {
     method @NonNull public java.util.List<android.app.appsearch.AppSearchSchema.PropertyConfig> getProperties();
     method @NonNull public String getSchemaType();
-    method @IntRange(from=0) public int getVersion();
+    method @Deprecated @IntRange(from=0) public int getVersion();
   }
 
   public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
@@ -58,7 +69,7 @@
     ctor public AppSearchSchema.Builder(@NonNull String);
     method @NonNull public android.app.appsearch.AppSearchSchema.Builder addProperty(@NonNull android.app.appsearch.AppSearchSchema.PropertyConfig);
     method @NonNull public android.app.appsearch.AppSearchSchema build();
-    method @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int);
+    method @Deprecated @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int);
   }
 
   public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig {
@@ -130,7 +141,9 @@
   public final class AppSearchSession implements java.io.Closeable {
     method public void close();
     method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>);
-    method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>);
+    method public void getNamespaces(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<java.lang.String>>>);
+    method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GetSchemaResponse>>);
+    method public void getStorageInfo(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.StorageInfo>>);
     method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
     method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
     method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
@@ -203,16 +216,29 @@
     method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
   }
 
+  public class GetSchemaResponse extends java.util.HashSet<android.app.appsearch.AppSearchSchema> {
+    method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
+    method @IntRange(from=0) public int getVersion();
+  }
+
+  public static final class GetSchemaResponse.Builder {
+    ctor public GetSchemaResponse.Builder();
+    method @NonNull public android.app.appsearch.GetSchemaResponse.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema);
+    method @NonNull public android.app.appsearch.GetSchemaResponse build();
+    method @NonNull public android.app.appsearch.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int);
+  }
+
   public class GlobalSearchSession implements java.io.Closeable {
     method public void close();
+    method public void reportSystemUsage(@NonNull android.app.appsearch.ReportSystemUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
     method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec);
   }
 
   public abstract class Migrator {
     ctor public Migrator();
-    ctor public Migrator(int);
     method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onDowngrade(int, int, @NonNull android.app.appsearch.GenericDocument);
     method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onUpgrade(int, int, @NonNull android.app.appsearch.GenericDocument);
+    method public abstract boolean shouldMigrate(int, int);
   }
 
   public class PackageIdentifier {
@@ -246,6 +272,21 @@
     method @Deprecated @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
   }
 
+  public final class ReportSystemUsageRequest {
+    method @NonNull public String getDatabaseName();
+    method @NonNull public String getNamespace();
+    method @NonNull public String getPackageName();
+    method @NonNull public String getUri();
+    method public long getUsageTimeMillis();
+  }
+
+  public static final class ReportSystemUsageRequest.Builder {
+    ctor public ReportSystemUsageRequest.Builder(@NonNull String, @NonNull String, @NonNull String);
+    method @NonNull public android.app.appsearch.ReportSystemUsageRequest build();
+    method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUri(@NonNull String);
+    method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUsageTimeMillis(long);
+  }
+
   public final class ReportUsageRequest {
     method @NonNull public String getNamespace();
     method @NonNull public String getUri();
@@ -267,6 +308,7 @@
     method @NonNull public android.app.appsearch.GenericDocument getGenericDocument();
     method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
     method @NonNull public String getPackageName();
+    method public double getRankingSignal();
   }
 
   public static final class SearchResult.Builder {
@@ -274,6 +316,7 @@
     method @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo);
     method @NonNull public android.app.appsearch.SearchResult build();
     method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument);
+    method @NonNull public android.app.appsearch.SearchResult.Builder setRankingSignal(double);
   }
 
   public static final class SearchResult.MatchInfo {
@@ -315,9 +358,13 @@
     method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
     method public int getRankingStrategy();
     method public int getResultCountPerPage();
+    method public int getResultGroupingLimit();
+    method public int getResultGroupingTypeFlags();
     method public int getSnippetCount();
     method public int getSnippetCountPerProperty();
     method public int getTermMatch();
+    field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
+    field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1
     field public static final int ORDER_ASCENDING = 1; // 0x1
     field public static final int ORDER_DESCENDING = 0; // 0x0
     field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
@@ -325,6 +372,8 @@
     field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1
     field public static final int RANKING_STRATEGY_NONE = 0; // 0x0
     field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3
+    field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6
+    field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7
     field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4
     field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5
     field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1
@@ -345,6 +394,7 @@
     method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setRankingStrategy(int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_NUM_PER_PAGE) int);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder setResultGrouping(int, int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_COUNT) int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_PER_PROPERTY_COUNT) int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setTermMatch(int);
@@ -356,6 +406,7 @@
     method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem();
     method @Deprecated @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi();
     method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages();
+    method @IntRange(from=1) public int getVersion();
     method public boolean isForceOverride();
   }
 
@@ -366,9 +417,11 @@
     method @NonNull public android.app.appsearch.SetSchemaRequest build();
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.Migrator);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrators(@NonNull java.util.Map<java.lang.String,android.app.appsearch.Migrator>);
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(@NonNull String, boolean);
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier);
     method @Deprecated @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int);
   }
 
   public class SetSchemaResponse {
@@ -407,6 +460,20 @@
     method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setUri(@NonNull String);
   }
 
+  public class StorageInfo {
+    method public int getAliveDocumentsCount();
+    method public int getAliveNamespacesCount();
+    method public long getSizeBytes();
+  }
+
+  public static final class StorageInfo.Builder {
+    ctor public StorageInfo.Builder();
+    method @NonNull public android.app.appsearch.StorageInfo build();
+    method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveDocumentsCount(int);
+    method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveNamespacesCount(int);
+    method @NonNull public android.app.appsearch.StorageInfo.Builder setSizeBytes(long);
+  }
+
 }
 
 package android.app.appsearch.exceptions {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 31ab259..35cea3e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -30,10 +30,10 @@
 /**
  * Provides results for AppSearch batch operations which encompass multiple documents.
  *
- * <p>Individual results of a batch operation are separated into two maps: one for successes and one
- * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of
- * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link
- * AppSearchResult} objects.
+ * <p>Individual results of a batch operation are separated into two maps: one for successes and
+ * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to
+ * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to
+ * {@link AppSearchResult} objects.
  *
  * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
  * both successes and failures.
@@ -97,8 +97,8 @@
     }
 
     /**
-     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed
-     * individual results.
+     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
+     * failed individual results.
      *
      * <p>The values of the {@link Map} will not be {@code null}.
      */
@@ -120,7 +120,6 @@
 
     /**
      * Asserts that this {@link AppSearchBatchResult} has no failures.
-     *
      * @hide
      */
     public void checkSuccess() {
@@ -162,8 +161,6 @@
      * Builder for {@link AppSearchBatchResult} objects.
      *
      * <p>Once {@link #build} is called, the instance can no longer be used.
-     *
-     * @hide
      */
     public static final class Builder<KeyType, ValueType> {
         private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
@@ -178,6 +175,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getSuccesses
         @NonNull
         public Builder<KeyType, ValueType> setSuccess(
                 @NonNull KeyType key, @Nullable ValueType result) {
@@ -193,6 +191,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getFailures
         @NonNull
         public Builder<KeyType, ValueType> setFailure(
                 @NonNull KeyType key,
@@ -210,6 +209,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getAll
         @NonNull
         public Builder<KeyType, ValueType> setResult(
                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index 440f633..b66837d 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -37,20 +39,19 @@
 public final class AppSearchResult<ValueType> implements Parcelable {
     /**
      * 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,
+            RESULT_SECURITY_ERROR,
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -90,6 +91,9 @@
     /** The caller supplied a schema which is invalid or incompatible with the previous schema. */
     public static final int RESULT_INVALID_SCHEMA = 7;
 
+    /** The caller requested an operation it does not have privileges for. */
+    public static final int RESULT_SECURITY_ERROR = 8;
+
     private final @ResultCode int mResultCode;
     @Nullable private final ValueType mResultValue;
     @Nullable private final String mErrorMessage;
@@ -148,8 +152,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 AppSearchSession} 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() {
@@ -208,8 +212,6 @@
 
     /**
      * Creates a new successful {@link AppSearchResult}.
-     *
-     * @hide
      */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@@ -219,8 +221,6 @@
 
     /**
      * Creates a new failed {@link AppSearchResult}.
-     *
-     * @hide
      */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@@ -228,6 +228,20 @@
         return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage);
     }
 
+    /**
+     * Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type.
+     *
+     * @hide
+     */
+    @NonNull
+    public static <ValueType> AppSearchResult<ValueType> newFailedResult(
+            @NonNull AppSearchResult<?> otherFailedResult) {
+        Preconditions.checkState(!otherFailedResult.isSuccess(),
+                "Cannot convert a success result to a failed result");
+        return AppSearchResult.newFailedResult(
+                otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage());
+    }
+
     /** @hide */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 9ea73a9..ce4aad1 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -45,11 +45,13 @@
  */
 public final class AppSearchSession implements Closeable {
     private static final String TAG = "AppSearchSession";
+
     private final String mPackageName;
     private final String mDatabaseName;
     @UserIdInt
     private final int mUserId;
     private final IAppSearchManager mService;
+
     private boolean mIsMutated = false;
     private boolean mIsClosed = false;
 
@@ -148,6 +150,7 @@
                     schemasPackageAccessibleBundles,
                     request.isForceOverride(),
                     mUserId,
+                    request.getVersion(),
                     new IAppSearchResultCallback.Stub() {
                         public void onResult(AppSearchResult result) {
                             executor.execute(() -> {
@@ -176,7 +179,7 @@
      */
     public void getSchema(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<AppSearchResult<Set<AppSearchSchema>>> callback) {
+            @NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
@@ -189,14 +192,46 @@
                         public void onResult(AppSearchResult result) {
                             executor.execute(() -> {
                                 if (result.isSuccess()) {
-                                    List<Bundle> schemaBundles =
-                                            (List<Bundle>) result.getResultValue();
-                                    Set<AppSearchSchema> schemas = new ArraySet<>(
-                                            schemaBundles.size());
-                                    for (int i = 0; i < schemaBundles.size(); i++) {
-                                        schemas.add(new AppSearchSchema(schemaBundles.get(i)));
-                                    }
-                                    callback.accept(AppSearchResult.newSuccessfulResult(schemas));
+                                    Bundle responseBundle = (Bundle) result.getResultValue();
+                                    GetSchemaResponse response =
+                                            new GetSchemaResponse(responseBundle);
+                                    callback.accept(AppSearchResult.newSuccessfulResult(response));
+                                } else {
+                                    callback.accept(result);
+                                }
+                            });
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieves the set of all namespaces in the current database with at least one document.
+     *
+     * @param executor        Executor on which to invoke the callback.
+     * @param callback        Callback to receive the namespaces.
+     */
+    public void getNamespaces(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<AppSearchResult<Set<String>>> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
+        try {
+            mService.getNamespaces(
+                    mPackageName,
+                    mDatabaseName,
+                    mUserId,
+                    new IAppSearchResultCallback.Stub() {
+                        public void onResult(AppSearchResult result) {
+                            executor.execute(() -> {
+                                if (result.isSuccess()) {
+                                    Set<String> namespaces =
+                                            new ArraySet<>((List<String>) result.getResultValue());
+                                    callback.accept(
+                                            AppSearchResult.newSuccessfulResult(namespaces));
                                 } else {
                                     callback.accept(result);
                                 }
@@ -437,6 +472,7 @@
                     request.getNamespace(),
                     request.getUri(),
                     request.getUsageTimeMillis(),
+                    /*systemUsage=*/ false,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
                         public void onResult(AppSearchResult result) {
@@ -541,6 +577,25 @@
     }
 
     /**
+     * Gets the storage info for this {@link AppSearchSession} database.
+     *
+     * <p>This may take time proportional to the number of documents and may be inefficient to
+     * call repeatedly.
+     *
+     * @param executor        Executor on which to invoke the callback.
+     * @param callback        Callback to receive the storage info.
+     */
+    public void getStorageInfo(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<AppSearchResult<StorageInfo>> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
+        // TODO(b/182909475): Implement getStorageInfo
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Closes the {@link AppSearchSession} to persist all schema and document updates, additions,
      * and deletes to disk.
      */
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index fb63e16..b30ed50 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -35,14 +36,15 @@
  * <p>Apps can retrieve indexed documents through the {@link #search} API.
  */
 public class GlobalSearchSession implements Closeable {
-
-    private final IAppSearchManager mService;
-
-    @UserIdInt
-    private final int mUserId;
-    private boolean mIsClosed = false;
+    private static final String TAG = "AppSearchGlobalSearchSe";
 
     private final String mPackageName;
+    @UserIdInt
+    private final int mUserId;
+    private final IAppSearchManager mService;
+
+    private boolean mIsMutated = false;
+    private boolean mIsClosed = false;
 
     /**
      * Creates a search session for the client, defined by the {@code userId} and
@@ -117,9 +119,66 @@
                 searchSpec, mUserId);
     }
 
-    /** Closes the {@link GlobalSearchSession}. */
+    /**
+     * Reports that a particular document has been used from a system surface.
+     *
+     * <p>See {@link AppSearchSession#reportUsage} for a general description of document usage, as
+     * well as an API that can be used by the app itself.
+     *
+     * <p>Usage reported via this method is accounted separately from usage reported via
+     * {@link AppSearchSession#reportUsage} and may be accessed using the constants
+     * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and
+     * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}.
+     *
+     * @param request The usage reporting request.
+     * @param executor Executor on which to invoke the callback.
+     * @param callback Callback to receive errors. If the operation succeeds, the callback will be
+     *                 invoked with an {@link AppSearchResult} whose value is {@code null}. The
+     *                 callback will be invoked with an {@link AppSearchResult} of
+     *                 {@link AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an
+     *                 app which is not part of the system.
+     */
+    public void reportSystemUsage(
+            @NonNull ReportSystemUsageRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<AppSearchResult<Void>> callback) {
+        Objects.requireNonNull(request);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed");
+        try {
+            mService.reportUsage(
+                    request.getPackageName(),
+                    request.getDatabaseName(),
+                    request.getNamespace(),
+                    request.getUri(),
+                    request.getUsageTimeMillis(),
+                    /*systemUsage=*/ true,
+                    mUserId,
+                    new IAppSearchResultCallback.Stub() {
+                        public void onResult(AppSearchResult result) {
+                            executor.execute(() -> callback.accept(result));
+                        }
+                    });
+            mIsMutated = true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Closes the {@link GlobalSearchSession}. Persists all mutations, including usage reports, to
+     * disk.
+     */
     @Override
     public void close() {
-        mIsClosed = true;
+        if (mIsMutated && !mIsClosed) {
+            try {
+                mService.persistToDisk(mUserId);
+                mIsClosed = true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to close the GlobalSearchSession", e);
+            }
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index ba27762..d436488 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -51,6 +51,7 @@
         in Map<String, List<Bundle>> schemasPackageAccessibleBundles,
         boolean forceOverride,
         in int userId,
+        in int schemaVersion,
         in IAppSearchResultCallback callback);
 
     /**
@@ -60,8 +61,7 @@
      * @param databaseName  The name of the database to retrieve.
      * @param userId Id of the calling user
      * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
-     *     {@link AppSearchResult}&lt;{@link List}&lt;{@link Bundle}&gt;&gt;, where the value are
-     *     AppSearchSchema bundle.
+     *     {@link AppSearchResult}&lt;{@link Bundle}&gt; where the bundle is a GetSchemaResponse.
      */
     void getSchema(
         in String packageName,
@@ -70,6 +70,21 @@
         in IAppSearchResultCallback callback);
 
     /**
+     * Retrieves the set of all namespaces in the current database with at least one document.
+     *
+     * @param packageName The name of the package that owns the schema.
+     * @param databaseName  The name of the database to retrieve.
+     * @param userId Id of the calling user
+     * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+     *     {@link AppSearchResult}&lt;{@link List}&lt;{@link String}&gt;&gt;.
+     */
+    void getNamespaces(
+        in String packageName,
+        in String databaseName,
+        in int userId,
+        in IAppSearchResultCallback callback);
+
+    /**
      * Inserts documents into the index.
      *
      * @param packageName The name of the package that owns this document.
@@ -190,6 +205,7 @@
      * @param namespace Namespace the document being used belongs to.
      * @param uri URI of the document being used.
      * @param usageTimeMillis The timestamp at which the document was used.
+     * @param systemUsage Whether the usage was reported by a system app against another app's doc.
      * @param userId Id of the calling user
      * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
      *     {@link AppSearchResult}&lt;{@link Void}&gt;.
@@ -200,6 +216,7 @@
          in String namespace,
          in String uri,
          in long usageTimeMillis,
+         in boolean systemUsage,
          in int userId,
          in IAppSearchResultCallback callback);
 
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 55f0c80..79b7b75 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -46,7 +46,6 @@
  */
 public final class AppSearchSchema {
     private static final String SCHEMA_TYPE_FIELD = "schemaType";
-    private static final String VERSION_FIELD = "version";
     private static final String PROPERTIES_FIELD = "properties";
 
     private final Bundle mBundle;
@@ -78,9 +77,10 @@
         return mBundle.getString(SCHEMA_TYPE_FIELD, "");
     }
 
-    /** Returns the version of this {@link AppSearchSchema}. */
+    /** @deprecated Use {@link GetSchemaResponse#getVersion()} instead. */
+    @Deprecated
     public @IntRange(from = 0) int getVersion() {
-        return mBundle.getInt(VERSION_FIELD);
+        return 0;
     }
 
     /**
@@ -115,15 +115,12 @@
         if (!getSchemaType().equals(otherSchema.getSchemaType())) {
             return false;
         }
-        if (getVersion() != otherSchema.getVersion()) {
-            return false;
-        }
         return getProperties().equals(otherSchema.getProperties());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(getSchemaType(), getVersion(), getProperties());
+        return Objects.hash(getSchemaType(), getProperties());
     }
 
     /** Builder for {@link AppSearchSchema objects}. */
@@ -131,7 +128,6 @@
         private final String mSchemaType;
         private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
         private final Set<String> mPropertyNames = new ArraySet<>();
-        private int mVersion;
         private boolean mBuilt = false;
 
         /** Creates a new {@link AppSearchSchema.Builder}. */
@@ -154,38 +150,13 @@
         }
 
         /**
-         * Sets the version number of the {@link AppSearchSchema}.
-         *
-         * <p>The {@link AppSearchSession} database can only ever hold documents for one version of
-         * a {@link AppSearchSchema} type at a time.
-         *
-         * <p>Setting a version number that is different from the version number of the schema
-         * currently stored in AppSearch will result in AppSearch calling the {@link Migrator}
-         * provided to {@link AppSearchSession#setSchema} to migrate the documents already in
-         * AppSearch from the previous version to the one set in this request. The version number
-         * can be updated without any other changes to the schema.
-         *
-         * <p>The version number can stay the same, increase, or decrease relative to the current
-         * version number of the {@link AppSearchSchema} type that is already stored in the {@link
-         * AppSearchSession} database.
-         *
-         * <p>The version number will be updated if the {@link SetSchemaRequest} contains
-         * backwards-compatible changes or {@link SetSchemaRequest.Builder#setForceOverride} method
-         * is set to {@code true}.
-         *
-         * @param version A non-negative int number represents the version of this {@link
-         *     AppSearchSchema}, default version is 0.
-         * @throws IllegalStateException if the version is negative or the builder has already been
-         *     used.
-         * @see AppSearchSession#setSchema
-         * @see Migrator
-         * @see SetSchemaRequest.Builder#setMigrator
+         * @deprecated TODO(b/181887768): This method is a no-op and only exists for dogfooder
+         *     transition.
          */
+        @Deprecated
         @NonNull
         public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentNonnegative(version);
-            mVersion = version;
             return this;
         }
 
@@ -199,7 +170,6 @@
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Bundle bundle = new Bundle();
             bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType);
-            bundle.putInt(AppSearchSchema.VERSION_FIELD, mVersion);
             bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles);
             mBuilt = true;
             return new AppSearchSchema(bundle);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
new file mode 100644
index 0000000..3e69367
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -0,0 +1,116 @@
+/*
+ * 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.IntRange;
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/** The response class of {@link AppSearchSession#getSchema} */
+// TODO(b/181887768) extends only for dogfooder transition. */
+public class GetSchemaResponse extends HashSet<AppSearchSchema> {
+    private static final String VERSION_FIELD = "version";
+    private static final String SCHEMAS_FIELD = "schemas";
+
+    private final Bundle mBundle;
+
+    // TODO(b/181887768) Remove this method once this class no longer extends HashSet. */
+    private static Set<AppSearchSchema> getSchemasFromBundle(Bundle bundle) {
+        ArrayList<Bundle> schemaBundles = bundle.getParcelableArrayList(SCHEMAS_FIELD);
+        Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
+        for (int i = 0; i < schemaBundles.size(); i++) {
+            schemas.add(new AppSearchSchema(schemaBundles.get(i)));
+        }
+        return schemas;
+    }
+
+    GetSchemaResponse(@NonNull Bundle bundle) {
+        super(getSchemasFromBundle(Preconditions.checkNotNull(bundle)));
+        mBundle = bundle;
+    }
+
+    /**
+     * Returns the {@link Bundle} populated by this builder.
+     *
+     * @hide
+     */
+    @NonNull
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Returns the overall database schema version.
+     *
+     * <p>If the database is empty, 0 will be returned.
+     */
+    @IntRange(from = 0)
+    public int getVersion() {
+        return mBundle.getInt(VERSION_FIELD);
+    }
+
+    /**
+     * Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}.
+     */
+    @NonNull
+    public Set<AppSearchSchema> getSchemas() {
+        return this;
+    }
+
+    /** Builder for {@link GetSchemaResponse} objects. */
+    public static final class Builder {
+        private int mVersion;
+        private boolean mBuilt = false;
+        private final ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
+
+        /**
+         * Sets the database overall schema version.
+         *
+         * <p>Default version is 0
+         */
+        @NonNull
+        public Builder setVersion(@IntRange(from = 0) int version) {
+            mVersion = version;
+            return this;
+        }
+
+        /** Adds one {@link AppSearchSchema} to the schema list. */
+        @NonNull
+        public Builder addSchema(@NonNull AppSearchSchema schema) {
+            mSchemaBundles.add(schema.getBundle());
+            return this;
+        }
+
+        /** Builds a {@link GetSchemaResponse} object. */
+        @NonNull
+        public GetSchemaResponse build() {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Bundle bundle = new Bundle();
+            bundle.putInt(VERSION_FIELD, mVersion);
+            bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles);
+            mBuilt = true;
+            return new GetSchemaResponse(bundle);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java
index 5ae9a41..511b42a 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.WorkerThread;
 
-import com.android.internal.util.Preconditions;
-
 /**
  * A migrator class to translate {@link GenericDocument} from different version of {@link
  * AppSearchSchema}
@@ -37,37 +35,14 @@
  * documents won't have any observable changes.
  */
 public abstract class Migrator {
-    private final int mStartVersion;
-
     /**
-     * Creates a {@link Migrator} will trigger migration for any version less than the final version
-     * in the new schema.
-     */
-    public Migrator() {
-        this(/*startVersion=*/ 0);
-    }
-
-    /**
-     * Creates a {@link Migrator} with a non-negative start version.
+     * Returns {@code true} if this migrator's source type needs to be migrated to update from
+     * currentVersion to finalVersion.
      *
-     * <p>Providing 0 will trigger migration for any version less than the final version in the new
-     * schema.
-     *
-     * @param startVersion The migration will be only triggered for those versions greater or equal
-     *     to the given startVersion.
+     * <p>Migration won't be triggered if currentVersion is equal to finalVersion even if {@link
+     * #shouldMigrate} return true;
      */
-    public Migrator(int startVersion) {
-        Preconditions.checkArgumentNonnegative(startVersion);
-        mStartVersion = startVersion;
-    }
-
-    /**
-     * @return {@code True} if the current version need to be migrated.
-     * @hide
-     */
-    public boolean shouldMigrateToFinalVersion(int currentVersion, int finalVersion) {
-        return currentVersion >= mStartVersion && currentVersion != finalVersion;
-    }
+    public abstract boolean shouldMigrate(int currentVersion, int finalVersion);
 
     /**
      * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}.
@@ -75,17 +50,22 @@
      * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a higher
      * version number than the current {@link AppSearchSchema} saved in AppSearch.
      *
-     * <p>This method will be invoked on the background worker thread.
+     * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link
+     * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use
+     * the same URI.
+     *
+     * <p>This method will be invoked on the background worker thread provided via {@link
+     * AppSearchSession#setSchema}.
      *
      * @param currentVersion The current version of the document's schema.
-     * @param targetVersion The final version that documents need to be migrated to.
+     * @param finalVersion The final version that documents need to be migrated to.
      * @param document The {@link GenericDocument} need to be translated to new version.
      * @return A {@link GenericDocument} in new version.
      */
     @WorkerThread
     @NonNull
     public abstract GenericDocument onUpgrade(
-            int currentVersion, int targetVersion, @NonNull GenericDocument document);
+            int currentVersion, int finalVersion, @NonNull GenericDocument document);
 
     /**
      * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}.
@@ -93,15 +73,19 @@
      * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a lower
      * version number than the current {@link AppSearchSchema} saved in AppSearch.
      *
+     * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link
+     * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use
+     * the same URI.
+     *
      * <p>This method will be invoked on the background worker thread.
      *
      * @param currentVersion The current version of the document's schema.
-     * @param targetVersion The final version that documents need to be migrated to.
+     * @param finalVersion The final version that documents need to be migrated to.
      * @param document The {@link GenericDocument} need to be translated to new version.
      * @return A {@link GenericDocument} in new version.
      */
     @WorkerThread
     @NonNull
     public abstract GenericDocument onDowngrade(
-            int currentVersion, int targetVersion, @NonNull GenericDocument document);
+            int currentVersion, int finalVersion, @NonNull GenericDocument document);
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
new file mode 100644
index 0000000..2e152f8
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.android.internal.util.Preconditions;
+
+/**
+ * A request to report usage of a document owned by another app from a system UI surface.
+ *
+ * <p>Usage reported in this way is measured separately from usage reported via {@link
+ * AppSearchSession#reportUsage}.
+ *
+ * <p>See {@link GlobalSearchSession#reportSystemUsage} for a detailed description of usage
+ * reporting.
+ */
+public final class ReportSystemUsageRequest {
+    private final String mPackageName;
+    private final String mDatabase;
+    private final String mNamespace;
+    private final String mUri;
+    private final long mUsageTimeMillis;
+
+    ReportSystemUsageRequest(
+            @NonNull String packageName,
+            @NonNull String database,
+            @NonNull String namespace,
+            @NonNull String uri,
+            long usageTimeMillis) {
+        mPackageName = Preconditions.checkNotNull(packageName);
+        mDatabase = Preconditions.checkNotNull(database);
+        mNamespace = Preconditions.checkNotNull(namespace);
+        mUri = Preconditions.checkNotNull(uri);
+        mUsageTimeMillis = usageTimeMillis;
+    }
+
+    /** Returns the package name of the app which owns the document that was used. */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns the database in which the document that was used resides. */
+    @NonNull
+    public String getDatabaseName() {
+        return mDatabase;
+    }
+
+    /** Returns the namespace of the document that was used. */
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    /** Returns the URI of document that was used. */
+    @NonNull
+    public String getUri() {
+        return mUri;
+    }
+
+    /**
+     * Returns the timestamp in milliseconds of the usage report (the time at which the document was
+     * used).
+     *
+     * <p>The value is in the {@link System#currentTimeMillis} time base.
+     */
+    public long getUsageTimeMillis() {
+        return mUsageTimeMillis;
+    }
+
+    /** Builder for {@link ReportSystemUsageRequest} objects. */
+    public static final class Builder {
+        private final String mPackageName;
+        private final String mDatabase;
+        private final String mNamespace;
+        private String mUri;
+        private Long mUsageTimeMillis;
+        private boolean mBuilt = false;
+
+        /** Creates a {@link ReportSystemUsageRequest.Builder} instance. */
+        public Builder(
+                @NonNull String packageName, @NonNull String database, @NonNull String namespace) {
+            mPackageName = Preconditions.checkNotNull(packageName);
+            mDatabase = Preconditions.checkNotNull(database);
+            mNamespace = Preconditions.checkNotNull(namespace);
+        }
+
+        /**
+         * Sets the URI of the document being used.
+         *
+         * <p>This field is required.
+         *
+         * @throws IllegalStateException if the builder has already been used
+         */
+        @NonNull
+        public ReportSystemUsageRequest.Builder setUri(@NonNull String uri) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkNotNull(uri);
+            mUri = uri;
+            return this;
+        }
+
+        /**
+         * Sets the timestamp in milliseconds of the usage report (the time at which the document
+         * was used).
+         *
+         * <p>The value is in the {@link System#currentTimeMillis} time base.
+         *
+         * <p>If unset, this defaults to the current timestamp at the time that the {@link
+         * ReportSystemUsageRequest} is constructed.
+         *
+         * @throws IllegalStateException if the builder has already been used
+         */
+        @NonNull
+        public ReportSystemUsageRequest.Builder setUsageTimeMillis(long usageTimeMillis) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mUsageTimeMillis = usageTimeMillis;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link ReportSystemUsageRequest}.
+         *
+         * @throws NullPointerException if {@link #setUri} has never been called
+         * @throws IllegalStateException if the builder has already been used
+         */
+        @NonNull
+        public ReportSystemUsageRequest build() {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI");
+            if (mUsageTimeMillis == null) {
+                mUsageTimeMillis = System.currentTimeMillis();
+            }
+            mBuilt = true;
+            return new ReportSystemUsageRequest(
+                    mPackageName, mDatabase, mNamespace, mUri, mUsageTimeMillis);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index cb20849..e66056f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -47,6 +47,7 @@
     static final String MATCHES_FIELD = "matches";
     static final String PACKAGE_NAME_FIELD = "packageName";
     static final String DATABASE_NAME_FIELD = "databaseName";
+    static final String RANKING_SIGNAL_FIELD = "rankingSignal";
 
     @NonNull private final Bundle mBundle;
 
@@ -131,6 +132,35 @@
         return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD));
     }
 
+    /**
+     * Returns the ranking signal of the {@link GenericDocument}, according to the ranking strategy
+     * set in {@link SearchSpec.Builder#setRankingStrategy(int)}.
+     *
+     * <p>The meaning of the ranking signal and its value is determined by the selected ranking
+     * strategy:
+     *
+     * <ul>
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_NONE} - this value will be 0
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_DOCUMENT_SCORE} - the value returned by calling
+     *       {@link GenericDocument#getScore()} on the document returned by {@link #getDocument()}
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling
+     *       {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link
+     *       #getDocument()}
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_RELEVANCE_SCORE} - an arbitrary double value where a
+     *       higher value means more relevant
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} - the number of times usage has been
+     *       reported for the document returned by {@link #getDocument()}
+     *   <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} - the timestamp of the
+     *       most recent usage that has been reported for the document returned by {@link
+     *       #getDocument()}
+     * </ul>
+     *
+     * @return Ranking signal of the document
+     */
+    public double getRankingSignal() {
+        return mBundle.getDouble(RANKING_SIGNAL_FIELD);
+    }
+
     /** Builder for {@link SearchResult} objects. */
     public static final class Builder {
         private final Bundle mBundle = new Bundle();
@@ -173,6 +203,14 @@
             return this;
         }
 
+        /** Sets the ranking signal of the matched document in this SearchResult. */
+        @NonNull
+        public Builder setRankingSignal(double rankingSignal) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putDouble(RANKING_SIGNAL_FIELD, rankingSignal);
+            return this;
+        }
+
         /**
          * Constructs a new {@link SearchResult}.
          *
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 7888c8d..19d9430 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.IllegalSearchSpecException;
 import android.os.Bundle;
 import android.util.ArrayMap;
@@ -58,6 +59,8 @@
     static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";
     static final String MAX_SNIPPET_FIELD = "maxSnippet";
     static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks";
+    static final String RESULT_GROUPING_TYPE_FLAGS = "resultGroupingTypeFlags";
+    static final String RESULT_GROUPING_LIMIT = "resultGroupingLimit";
 
     /** @hide */
     public static final int DEFAULT_NUM_PER_PAGE = 10;
@@ -107,7 +110,9 @@
                 RANKING_STRATEGY_CREATION_TIMESTAMP,
                 RANKING_STRATEGY_RELEVANCE_SCORE,
                 RANKING_STRATEGY_USAGE_COUNT,
-                RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP
+                RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP,
+                RANKING_STRATEGY_SYSTEM_USAGE_COUNT,
+                RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface RankingStrategy {}
@@ -120,10 +125,14 @@
     public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
     /** Ranked by document relevance score. */
     public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3;
-    /** Ranked by number of usages. */
+    /** Ranked by number of usages, as reported by the app. */
     public static final int RANKING_STRATEGY_USAGE_COUNT = 4;
-    /** Ranked by timestamp of last usage. */
+    /** Ranked by timestamp of last usage, as reported by the app. */
     public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5;
+    /** Ranked by number of usages from a system UI surface. */
+    public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6;
+    /** Ranked by timestamp of last usage from a system UI surface. */
+    public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7;
 
     /**
      * Order for query result.
@@ -141,6 +150,28 @@
     /** Search results will be returned in an ascending order. */
     public static final int ORDER_ASCENDING = 1;
 
+    /**
+     * Grouping type for result limits.
+     *
+     * @hide
+     */
+    @IntDef(
+            flag = true,
+            value = {GROUPING_TYPE_PER_PACKAGE, GROUPING_TYPE_PER_NAMESPACE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GroupingType {}
+
+    /**
+     * Results should be grouped together by package for the purpose of enforcing a limit on the
+     * number of results returned per package.
+     */
+    public static final int GROUPING_TYPE_PER_PACKAGE = 0b01;
+    /**
+     * Results should be grouped together by namespace for the purpose of enforcing a limit on the
+     * number of results returned per namespace.
+     */
+    public static final int GROUPING_TYPE_PER_NAMESPACE = 0b10;
+
     private final Bundle mBundle;
 
     /** @hide */
@@ -259,6 +290,24 @@
         return typePropertyPathsMap;
     }
 
+    /**
+     * Get the type of grouping limit to apply, or 0 if {@link Builder#setResultGrouping} was not
+     * called.
+     */
+    public @GroupingType int getResultGroupingTypeFlags() {
+        return mBundle.getInt(RESULT_GROUPING_TYPE_FLAGS);
+    }
+
+    /**
+     * Get the maximum number of results to return for each group.
+     *
+     * @return the maximum number of results to return for each group or Integer.MAX_VALUE if {@link
+     *     Builder#setResultGrouping(int, int)} was not called.
+     */
+    public int getResultGroupingLimit() {
+        return mBundle.getInt(RESULT_GROUPING_LIMIT, Integer.MAX_VALUE);
+    }
+
     /** Builder for {@link SearchSpec objects}. */
     public static final class Builder {
 
@@ -391,7 +440,7 @@
             Preconditions.checkArgumentInRange(
                     rankingStrategy,
                     RANKING_STRATEGY_NONE,
-                    RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP,
+                    RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
                     "Result ranking strategy");
             mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
             return this;
@@ -549,6 +598,34 @@
         }
 
         /**
+         * Set the maximum number of results to return for each group, where groups are defined by
+         * grouping type.
+         *
+         * <p>Calling this method will override any previous calls. So calling
+         * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 7) and then calling
+         * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 2) will result in only the latter, a limit
+         * of two results per package, being applied. Or calling setResultGrouping
+         * (GROUPING_TYPE_PER_PACKAGE, 1) and then calling setResultGrouping
+         * (GROUPING_TYPE_PER_PACKAGE | GROUPING_PER_NAMESPACE, 5) will result in five results per
+         * package per namespace.
+         *
+         * @param groupingTypeFlags One or more combination of grouping types.
+         * @param limit Number of results to return per {@code groupingTypeFlags}.
+         * @throws IllegalArgumentException if groupingTypeFlags is zero.
+         */
+        // Individual parameters available from getResultGroupingTypeFlags and
+        // getResultGroupingLimit
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) {
+            Preconditions.checkState(
+                    groupingTypeFlags != 0, "Result grouping type cannot be zero.");
+            mBundle.putInt(RESULT_GROUPING_TYPE_FLAGS, groupingTypeFlags);
+            mBundle.putInt(RESULT_GROUPING_LIMIT, limit);
+            return this;
+        }
+
+        /**
          * Constructs a new {@link SearchSpec} from the contents of this builder.
          *
          * <p>After calling this method, the builder must no longer be used.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index c1eedcd..e840ffc 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -16,6 +16,7 @@
 
 package android.app.appsearch;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.util.ArrayMap;
@@ -84,18 +85,21 @@
     private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
     private final Map<String, Migrator> mMigrators;
     private final boolean mForceOverride;
+    private final int mVersion;
 
     SetSchemaRequest(
             @NonNull Set<AppSearchSchema> schemas,
             @NonNull Set<String> schemasNotDisplayedBySystem,
             @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
             @NonNull Map<String, Migrator> migrators,
-            boolean forceOverride) {
+            boolean forceOverride,
+            int version) {
         mSchemas = Preconditions.checkNotNull(schemas);
         mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem);
         mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
         mMigrators = Preconditions.checkNotNull(migrators);
         mForceOverride = forceOverride;
+        mVersion = version;
     }
 
     /** Returns the {@link AppSearchSchema} types that are part of this request. */
@@ -166,6 +170,12 @@
         return mForceOverride;
     }
 
+    /** Returns the database overall schema version. */
+    @IntRange(from = 1)
+    public int getVersion() {
+        return mVersion;
+    }
+
     /**
      * Builder for {@link SetSchemaRequest} objects.
      *
@@ -178,6 +188,7 @@
                 new ArrayMap<>();
         private final Map<String, Migrator> mMigrators = new ArrayMap<>();
         private boolean mForceOverride = false;
+        private int mVersion = 1;
         private boolean mBuilt = false;
 
         /**
@@ -323,6 +334,19 @@
         }
 
         /**
+         * Sets {@link Migrator}s.
+         *
+         * @param migrators A {@link Map} of migrators that translate a document from its old
+         *     version to a new incompatible version.
+         */
+        @NonNull
+        public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
+            Preconditions.checkNotNull(migrators);
+            mMigrators.putAll(migrators);
+            return this;
+        }
+
+        /**
          * Sets whether or not to override the current schema in the {@link AppSearchSession}
          * database.
          *
@@ -340,6 +364,37 @@
         }
 
         /**
+         * Sets the version number of the overall {@link AppSearchSchema} in the database.
+         *
+         * <p>The {@link AppSearchSession} database can only ever hold documents for one version at
+         * a time.
+         *
+         * <p>Setting a version number that is different from the version number currently stored in
+         * AppSearch will result in AppSearch calling the {@link Migrator}s provided to {@link
+         * AppSearchSession#setSchema} to migrate the documents already in AppSearch from the
+         * previous version to the one set in this request. The version number can be updated
+         * without any other changes to the set of schemas.
+         *
+         * <p>The version number can stay the same, increase, or decrease relative to the current
+         * version number that is already stored in the {@link AppSearchSession} database.
+         *
+         * @param version A positive integer representing the version of the entire set of schemas
+         *     represents the version of the whole schema in the {@link AppSearchSession} database,
+         *     default version is 1.
+         * @throws IllegalStateException if the version is negative or the builder has already been
+         *     used.
+         * @see AppSearchSession#setSchema
+         * @see Migrator
+         * @see SetSchemaRequest.Builder#setMigrator
+         */
+        @NonNull
+        public Builder setVersion(@IntRange(from = 1) int version) {
+            Preconditions.checkArgument(version >= 1, "Version must be a positive number.");
+            mVersion = version;
+            return this;
+        }
+
+        /**
          * Builds a new {@link SetSchemaRequest} object.
          *
          * @throws IllegalArgumentException if schema types were referenced, but the corresponding
@@ -372,7 +427,8 @@
                     mSchemasNotDisplayedBySystem,
                     mSchemasVisibleToPackages,
                     mMigrators,
-                    mForceOverride);
+                    mForceOverride,
+                    mVersion);
         }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
new file mode 100644
index 0000000..dc04cf3
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+/** The response class of {@code AppSearchSession#getStorageInfo}. */
+public class StorageInfo {
+
+    private static final String SIZE_BYTES_FIELD = "sizeBytes";
+    private static final String ALIVE_DOCUMENTS_COUNT = "aliveDocumentsCount";
+    private static final String ALIVE_NAMESPACES_COUNT = "aliveNamespacesCount";
+
+    private final Bundle mBundle;
+
+    StorageInfo(@NonNull Bundle bundle) {
+        mBundle = Preconditions.checkNotNull(bundle);
+    }
+
+    /**
+     * Returns the {@link Bundle} populated by this builder.
+     *
+     * @hide
+     */
+    @NonNull
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /** Returns the estimated size of the session's database in bytes. */
+    public long getSizeBytes() {
+        return mBundle.getLong(SIZE_BYTES_FIELD);
+    }
+
+    /**
+     * Returns the number of alive documents in the current session.
+     *
+     * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as
+     * set in {@link GenericDocument.Builder#setTtlMillis}.
+     */
+    public int getAliveDocumentsCount() {
+        return mBundle.getInt(ALIVE_DOCUMENTS_COUNT);
+    }
+
+    /**
+     * Returns the number of namespaces that have at least one alive document in the current
+     * session's database.
+     *
+     * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as
+     * set in {@link GenericDocument.Builder#setTtlMillis}.
+     */
+    public int getAliveNamespacesCount() {
+        return mBundle.getInt(ALIVE_NAMESPACES_COUNT);
+    }
+
+    /** Builder for {@link StorageInfo} objects. */
+    public static final class Builder {
+        private final Bundle mBundle = new Bundle();
+        private boolean mBuilt = false;
+
+        /** Sets the size in bytes. */
+        @NonNull
+        public StorageInfo.Builder setSizeBytes(long sizeBytes) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putLong(SIZE_BYTES_FIELD, sizeBytes);
+            return this;
+        }
+
+        /** Sets the number of alive documents. */
+        @NonNull
+        public StorageInfo.Builder setAliveDocumentsCount(int numAliveDocuments) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putInt(ALIVE_DOCUMENTS_COUNT, numAliveDocuments);
+            return this;
+        }
+
+        /** Sets the number of alive namespaces. */
+        @NonNull
+        public StorageInfo.Builder setAliveNamespacesCount(int numAliveNamespaces) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putInt(ALIVE_NAMESPACES_COUNT, numAliveNamespaces);
+            return this;
+        }
+
+        /** Builds a {@link StorageInfo} object. */
+        @NonNull
+        public StorageInfo build() {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBuilt = true;
+            return new StorageInfo(mBundle);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
index fae8ad4..32d7e043 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
@@ -20,12 +20,11 @@
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.Migrator;
+import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Log;
 
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -36,83 +35,70 @@
  * @hide
  */
 public final class SchemaMigrationUtil {
-    private static final String TAG = "AppSearchMigrateUtil";
-
     private SchemaMigrationUtil() {}
 
-    /**
-     * Finds out which incompatible schema type won't be migrated by comparing its current and final
-     * version number.
-     */
+    /** Returns all active {@link Migrator}s that need to be triggered in this migration. */
     @NonNull
-    public static Set<String> getUnmigratedIncompatibleTypes(
-            @NonNull Set<String> incompatibleSchemaTypes,
+    public static Map<String, Migrator> getActiveMigrators(
+            @NonNull Set<AppSearchSchema> existingSchemas,
             @NonNull Map<String, Migrator> migrators,
-            @NonNull Map<String, Integer> currentVersionMap,
-            @NonNull Map<String, Integer> finalVersionMap)
-            throws AppSearchException {
-        Set<String> unmigratedSchemaTypes = new ArraySet<>();
-        for (String unmigratedSchemaType : incompatibleSchemaTypes) {
-            Integer currentVersion = currentVersionMap.get(unmigratedSchemaType);
-            Integer finalVersion = finalVersionMap.get(unmigratedSchemaType);
-            if (currentVersion == null) {
-                // impossible, we have done something wrong.
-                throw new AppSearchException(
-                        AppSearchResult.RESULT_UNKNOWN_ERROR,
-                        "Cannot find the current version number for schema type: "
-                                + unmigratedSchemaType);
-            }
-            if (finalVersion == null) {
-                // The schema doesn't exist in the SetSchemaRequest.
-                unmigratedSchemaTypes.add(unmigratedSchemaType);
-                continue;
-            }
-            // we don't have migrator or won't trigger migration for this schema type.
-            Migrator migrator = migrators.get(unmigratedSchemaType);
-            if (migrator == null
-                    || !migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion)) {
-                unmigratedSchemaTypes.add(unmigratedSchemaType);
+            int currentVersion,
+            int finalVersion) {
+        if (currentVersion == finalVersion) {
+            return Collections.emptyMap();
+        }
+        Set<String> existingTypes = new ArraySet<>(existingSchemas.size());
+        for (AppSearchSchema schema : existingSchemas) {
+            existingTypes.add(schema.getSchemaType());
+        }
+
+        Map<String, Migrator> activeMigrators = new ArrayMap<>();
+        for (Map.Entry<String, Migrator> entry : migrators.entrySet()) {
+            // The device contains the source type, and we should trigger migration for the type.
+            String schemaType = entry.getKey();
+            Migrator migrator = entry.getValue();
+            if (existingTypes.contains(schemaType)
+                    && migrator.shouldMigrate(currentVersion, finalVersion)) {
+                activeMigrators.put(schemaType, migrator);
             }
         }
-        return Collections.unmodifiableSet(unmigratedSchemaTypes);
+        return activeMigrators;
     }
 
     /**
-     * Triggers upgrade or downgrade migration for the given schema type if its version stored in
-     * AppSearch is different with the version in the request.
-     *
-     * @return {@code True} if we trigger the migration for the given type.
+     * Checks the setSchema() call won't delete any types or has incompatible types after all {@link
+     * Migrator} has been triggered..
      */
-    public static boolean shouldTriggerMigration(
-            @NonNull String schemaType,
-            @NonNull Migrator migrator,
-            @NonNull Map<String, Integer> currentVersionMap,
-            @NonNull Map<String, Integer> finalVersionMap)
+    public static void checkDeletedAndIncompatibleAfterMigration(
+            @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators)
             throws AppSearchException {
-        Integer currentVersion = currentVersionMap.get(schemaType);
-        Integer finalVersion = finalVersionMap.get(schemaType);
-        if (currentVersion == null) {
-            Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch.");
-            return false;
-        }
-        if (finalVersion == null) {
-            throw new AppSearchException(
-                    AppSearchResult.RESULT_INVALID_ARGUMENT,
-                    "Receive a migrator for schema type : "
-                            + schemaType
-                            + ", but the schema doesn't exist in the request.");
-        }
-        return migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion);
+        Set<String> unmigratedIncompatibleTypes =
+                new ArraySet<>(setSchemaResponse.getIncompatibleTypes());
+        unmigratedIncompatibleTypes.removeAll(activeMigrators);
+
+        Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes());
+        unmigratedDeletedTypes.removeAll(activeMigrators);
+
+        // check if there are any unmigrated incompatible types or deleted types. If there
+        // are, we will getActiveMigratorsthrow an exception. That's the only case we
+        // swallowed in the AppSearchImpl#setSchema().
+        // Since the force override is false, the schema will not have been set if there are
+        // any incompatible or deleted types.
+        checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes);
     }
 
-    /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */
-    @NonNull
-    public static Map<String, Integer> buildVersionMap(
-            @NonNull Collection<AppSearchSchema> schemas) {
-        Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size());
-        for (AppSearchSchema currentSchema : schemas) {
-            currentVersionMap.put(currentSchema.getSchemaType(), currentSchema.getVersion());
+    /** Checks the setSchema() call won't delete any types or has incompatible types. */
+    public static void checkDeletedAndIncompatible(
+            @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes)
+            throws AppSearchException {
+        if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) {
+            String newMessage =
+                    "Schema is incompatible."
+                            + "\n  Deleted types: "
+                            + deletedTypes
+                            + "\n  Incompatible types: "
+                            + incompatibleTypes;
+            throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage);
         }
-        return currentVersionMap;
     }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 2806974..6e3fb82 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -24,6 +24,7 @@
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.IAppSearchBatchResultCallback;
 import android.app.appsearch.IAppSearchManager;
 import android.app.appsearch.IAppSearchResultCallback;
@@ -96,6 +97,7 @@
                 @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
                 boolean forceOverride,
                 @UserIdInt int userId,
+                int schemaVersion,
                 @NonNull IAppSearchResultCallback callback) {
             Preconditions.checkNotNull(packageName);
             Preconditions.checkNotNull(databaseName);
@@ -129,7 +131,8 @@
                         schemas,
                         schemasNotDisplayedBySystem,
                         schemasPackageAccessible,
-                        forceOverride);
+                        forceOverride,
+                        schemaVersion);
                 invokeCallbackOnResult(
                         callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
             } catch (Throwable t) {
@@ -156,13 +159,35 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchImpl impl =
                         mImplInstanceManager.getAppSearchImpl(callingUserId);
-                List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName);
-                List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
-                for (int i = 0; i < schemas.size(); i++) {
-                    schemaBundles.add(schemas.get(i).getBundle());
-                }
+                GetSchemaResponse response = impl.getSchema(packageName, databaseName);
                 invokeCallbackOnResult(
-                        callback, AppSearchResult.newSuccessfulResult(schemaBundles));
+                        callback, AppSearchResult.newSuccessfulResult(response.getBundle()));
+            } catch (Throwable t) {
+                invokeCallbackOnError(callback, t);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public void getNamespaces(
+                @NonNull String packageName,
+                @NonNull String databaseName,
+                @UserIdInt int userId,
+                @NonNull IAppSearchResultCallback callback) {
+            Preconditions.checkNotNull(packageName);
+            Preconditions.checkNotNull(databaseName);
+            Preconditions.checkNotNull(callback);
+            int callingUid = Binder.getCallingUidOrThrow();
+            int callingUserId = handleIncomingUser(userId, callingUid);
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                verifyUserUnlocked(callingUserId);
+                verifyCallingPackage(callingUid, packageName);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(callingUserId);
+                List<String> namespaces = impl.getNamespaces(packageName, databaseName);
+                invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(namespaces));
             } catch (Throwable t) {
                 invokeCallbackOnError(callback, t);
             } finally {
@@ -380,6 +405,7 @@
                 @NonNull String namespace,
                 @NonNull String uri,
                 long usageTimeMillis,
+                boolean systemUsage,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
             Objects.requireNonNull(databaseName);
@@ -391,9 +417,16 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyUserUnlocked(callingUserId);
+
+                if (systemUsage) {
+                    // TODO(b/183031844): Validate that the call comes from the system
+                }
+
                 AppSearchImpl impl =
                         mImplInstanceManager.getAppSearchImpl(callingUserId);
-                impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis);
+                impl.reportUsage(
+                        packageName, databaseName, namespace, uri,
+                        usageTimeMillis, systemUsage);
                 invokeCallbackOnResult(
                         callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
             } catch (Throwable t) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index ad94a0a..7847429 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -24,6 +24,7 @@
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
@@ -73,6 +74,9 @@
     /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
     private static final String VISIBILITY_TYPE = "VisibilityType";
 
+    /** Version for the visibility schema */
+    private static final int SCHEMA_VERSION = 0;
+
     /**
      * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
      */
@@ -218,11 +222,10 @@
      * @throws AppSearchException AppSearchException on AppSearchImpl error.
      */
     public void initialize() throws AppSearchException {
-        List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME);
+        GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME);
         boolean hasVisibilityType = false;
         boolean hasPackageAccessibleType = false;
-        for (int i = 0; i < schemas.size(); i++) {
-            AppSearchSchema schema = schemas.get(i);
+        for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
             if (schema.getSchemaType().equals(VISIBILITY_TYPE)) {
                 hasVisibilityType = true;
             } else if (schema.getSchemaType().equals(PACKAGE_ACCESSIBLE_TYPE)) {
@@ -242,7 +245,8 @@
                     Arrays.asList(VISIBILITY_SCHEMA, PACKAGE_ACCESSIBLE_SCHEMA),
                     /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                     /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                    /*forceOverride=*/ false);
+                    /*forceOverride=*/ false,
+                    /*version=*/ SCHEMA_VERSION);
         }
 
         // Populate visibility settings set
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 5e8760e..de9d609 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
@@ -23,10 +23,12 @@
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.StorageInfo;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 import android.os.Bundle;
@@ -51,6 +53,7 @@
 import com.google.android.icing.proto.DeleteByQueryResultProto;
 import com.google.android.icing.proto.DeleteResultProto;
 import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.DocumentStorageInfoProto;
 import com.google.android.icing.proto.GetAllNamespacesResultProto;
 import com.google.android.icing.proto.GetOptimizeInfoResultProto;
 import com.google.android.icing.proto.GetResultProto;
@@ -58,8 +61,10 @@
 import com.google.android.icing.proto.GetSchemaResultProto;
 import com.google.android.icing.proto.IcingSearchEngineOptions;
 import com.google.android.icing.proto.InitializeResultProto;
+import com.google.android.icing.proto.NamespaceStorageInfoProto;
 import com.google.android.icing.proto.OptimizeResultProto;
 import com.google.android.icing.proto.PersistToDiskResultProto;
+import com.google.android.icing.proto.PersistType;
 import com.google.android.icing.proto.PropertyConfigProto;
 import com.google.android.icing.proto.PropertyProto;
 import com.google.android.icing.proto.PutResultProto;
@@ -73,6 +78,7 @@
 import com.google.android.icing.proto.SearchSpecProto;
 import com.google.android.icing.proto.SetSchemaResultProto;
 import com.google.android.icing.proto.StatusProto;
+import com.google.android.icing.proto.StorageInfoResultProto;
 import com.google.android.icing.proto.TypePropertyMask;
 import com.google.android.icing.proto.UsageReport;
 
@@ -295,11 +301,12 @@
      * @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.
+     * @param version The overall version number of the request.
+     * @return The response contains deleted schema types and incompatible schema types of this
+     *     call.
      * @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 SetSchemaResponse setSchema(
@@ -308,7 +315,8 @@
             @NonNull List<AppSearchSchema> schemas,
             @NonNull List<String> schemasNotPlatformSurfaceable,
             @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible,
-            boolean forceOverride)
+            boolean forceOverride,
+            int version)
             throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
@@ -320,7 +328,7 @@
             for (int i = 0; i < schemas.size(); i++) {
                 AppSearchSchema schema = schemas.get(i);
                 SchemaTypeConfigProto schemaTypeProto =
-                        SchemaToProtoConverter.toSchemaTypeConfigProto(schema);
+                        SchemaToProtoConverter.toSchemaTypeConfigProto(schema, version);
                 newSchemaBuilder.addTypes(schemaTypeProto);
             }
 
@@ -394,8 +402,8 @@
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public List<AppSearchSchema> getSchema(
-            @NonNull String packageName, @NonNull String databaseName) throws AppSearchException {
+    public GetSchemaResponse getSchema(@NonNull String packageName, @NonNull String databaseName)
+            throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
             throwIfClosedLocked();
@@ -403,7 +411,12 @@
             SchemaProto fullSchema = getSchemaProtoLocked();
 
             String prefix = createPrefix(packageName, databaseName);
-            List<AppSearchSchema> result = new ArrayList<>();
+            GetSchemaResponse.Builder responseBuilder = new GetSchemaResponse.Builder();
+            if (!fullSchema.getTypesList().isEmpty()) {
+                // TODO(b/183050495) find a place to store the version for the database, rather
+                // than read from a schema.
+                responseBuilder.setVersion(fullSchema.getTypes(0).getVersion());
+            }
             for (int i = 0; i < fullSchema.getTypesCount(); i++) {
                 String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
                 if (!prefix.equals(typePrefix)) {
@@ -431,9 +444,44 @@
 
                 AppSearchSchema schema =
                         SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
-                result.add(schema);
+                responseBuilder.addSchema(schema);
             }
-            return result;
+            return responseBuilder.build();
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Retrieves the list of namespaces with at least one document for this package name, database.
+     *
+     * <p>This method belongs to query group.
+     *
+     * @param packageName Package name that owns this schema
+     * @param databaseName The name of the database where this schema lives.
+     * @throws AppSearchException on IcingSearchEngine error.
+     */
+    @NonNull
+    public List<String> getNamespaces(@NonNull String packageName, @NonNull String databaseName)
+            throws AppSearchException {
+        mReadWriteLock.readLock().lock();
+        try {
+            throwIfClosedLocked();
+            // We can't just use mNamespaceMap here because we have no way to prune namespaces from
+            // mNamespaceMap when they have no more documents (e.g. after setting schema to empty or
+            // using deleteByQuery).
+            GetAllNamespacesResultProto getAllNamespacesResultProto =
+                    mIcingSearchEngineLocked.getAllNamespaces();
+            checkSuccess(getAllNamespacesResultProto.getStatus());
+            String prefix = createPrefix(packageName, databaseName);
+            List<String> results = new ArrayList<>();
+            for (int i = 0; i < getAllNamespacesResultProto.getNamespacesCount(); i++) {
+                String prefixedNamespace = getAllNamespacesResultProto.getNamespaces(i);
+                if (prefixedNamespace.startsWith(prefix)) {
+                    results.add(prefixedNamespace.substring(prefix.length()));
+                }
+            }
+            return results;
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -749,6 +797,18 @@
         ResultSpecProto.Builder resultSpecBuilder =
                 SearchSpecToProtoConverter.toResultSpecProto(searchSpec).toBuilder();
 
+        int groupingType = searchSpec.getResultGroupingTypeFlags();
+        if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0
+                && (groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) {
+            addPerPackagePerNamespaceResultGroupingsLocked(
+                    resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit());
+        } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0) {
+            addPerPackageResultGroupingsLocked(
+                    resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit());
+        } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) {
+            addPerNamespaceResultGroupingsLocked(
+                    resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit());
+        }
         rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes, allowedPrefixedSchemas);
 
         ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
@@ -810,19 +870,24 @@
             @NonNull String databaseName,
             @NonNull String namespace,
             @NonNull String uri,
-            long usageTimestampMillis)
+            long usageTimestampMillis,
+            boolean systemUsage)
             throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             throwIfClosedLocked();
 
             String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
+            UsageReport.UsageType usageType =
+                    systemUsage
+                            ? UsageReport.UsageType.USAGE_TYPE2
+                            : UsageReport.UsageType.USAGE_TYPE1;
             UsageReport report =
                     UsageReport.newBuilder()
                             .setDocumentNamespace(prefixedNamespace)
                             .setDocumentUri(uri)
                             .setUsageTimestampMs(usageTimestampMillis)
-                            .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
+                            .setUsageType(usageType)
                             .build();
 
             ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report);
@@ -919,6 +984,124 @@
         }
     }
 
+    /** Estimates the storage usage info for a specific package. */
+    @NonNull
+    public StorageInfo getStorageInfoForPackage(@NonNull String packageName)
+            throws AppSearchException {
+        mReadWriteLock.readLock().lock();
+        try {
+            throwIfClosedLocked();
+
+            Map<String, Set<String>> packageToDatabases = getPackageToDatabases();
+            Set<String> databases = packageToDatabases.get(packageName);
+            if (databases == null) {
+                // Package doesn't exist, no storage info to report
+                return new StorageInfo.Builder().build();
+            }
+
+            // Accumulate all the namespaces we're interested in.
+            Set<String> wantedPrefixedNamespaces = new ArraySet<>();
+            for (String database : databases) {
+                Set<String> prefixedNamespaces =
+                        mNamespaceMapLocked.get(createPrefix(packageName, database));
+                if (prefixedNamespaces != null) {
+                    wantedPrefixedNamespaces.addAll(prefixedNamespaces);
+                }
+            }
+            if (wantedPrefixedNamespaces.isEmpty()) {
+                return new StorageInfo.Builder().build();
+            }
+
+            return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
+    }
+
+    /** Estimates the storage usage info for a specific database in a package. */
+    @NonNull
+    public StorageInfo getStorageInfoForDatabase(
+            @NonNull String packageName, @NonNull String databaseName) throws AppSearchException {
+        mReadWriteLock.readLock().lock();
+        try {
+            throwIfClosedLocked();
+
+            Map<String, Set<String>> packageToDatabases = getPackageToDatabases();
+            Set<String> databases = packageToDatabases.get(packageName);
+            if (databases == null) {
+                // Package doesn't exist, no storage info to report
+                return new StorageInfo.Builder().build();
+            }
+            if (!databases.contains(databaseName)) {
+                // Database doesn't exist, no storage info to report
+                return new StorageInfo.Builder().build();
+            }
+
+            Set<String> wantedPrefixedNamespaces =
+                    mNamespaceMapLocked.get(createPrefix(packageName, databaseName));
+            if (wantedPrefixedNamespaces == null || wantedPrefixedNamespaces.isEmpty()) {
+                return new StorageInfo.Builder().build();
+            }
+
+            return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
+    }
+
+    @GuardedBy("mReadWriteLock")
+    @NonNull
+    private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces)
+            throws AppSearchException {
+        StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
+        checkSuccess(storageInfoResult.getStatus());
+        if (!storageInfoResult.hasStorageInfo()
+                || !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) {
+            return new StorageInfo.Builder().build();
+        }
+        long totalStorageSize = storageInfoResult.getStorageInfo().getTotalStorageSize();
+
+        DocumentStorageInfoProto documentStorageInfo =
+                storageInfoResult.getStorageInfo().getDocumentStorageInfo();
+        int totalDocuments =
+                documentStorageInfo.getNumAliveDocuments()
+                        + documentStorageInfo.getNumExpiredDocuments();
+
+        if (totalStorageSize == 0 || totalDocuments == 0) {
+            // Maybe we can exit early and also avoid a divide by 0 error.
+            return new StorageInfo.Builder().build();
+        }
+
+        // Accumulate stats across the package's namespaces.
+        int aliveDocuments = 0;
+        int expiredDocuments = 0;
+        int aliveNamespaces = 0;
+        List<NamespaceStorageInfoProto> namespaceStorageInfos =
+                documentStorageInfo.getNamespaceStorageInfoList();
+        for (int i = 0; i < namespaceStorageInfos.size(); i++) {
+            NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfos.get(i);
+            // The namespace from icing lib is already the prefixed format
+            if (prefixedNamespaces.contains(namespaceStorageInfo.getNamespace())) {
+                if (namespaceStorageInfo.getNumAliveDocuments() > 0) {
+                    aliveNamespaces++;
+                    aliveDocuments += namespaceStorageInfo.getNumAliveDocuments();
+                }
+                expiredDocuments += namespaceStorageInfo.getNumExpiredDocuments();
+            }
+        }
+        int namespaceDocuments = aliveDocuments + expiredDocuments;
+
+        // Since we don't have the exact size of all the documents, we do an estimation. Note
+        // that while the total storage takes into account schema, index, etc. in addition to
+        // documents, we'll only calculate the percentage based on number of documents a
+        // client has.
+        return new StorageInfo.Builder()
+                .setSizeBytes((long) (namespaceDocuments * 1.0 / totalDocuments * totalStorageSize))
+                .setAliveDocumentsCount(aliveDocuments)
+                .setAliveNamespacesCount(aliveNamespaces)
+                .build();
+    }
+
     /**
      * Persists all update/delete requests to the disk.
      *
@@ -937,7 +1120,7 @@
             throwIfClosedLocked();
 
             PersistToDiskResultProto persistToDiskResultProto =
-                    mIcingSearchEngineLocked.persistToDisk();
+                    mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL);
             checkSuccess(persistToDiskResultProto.getStatus());
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -1275,6 +1458,153 @@
                 .addAllTypePropertyMasks(prefixedTypePropertyMasks);
     }
 
+    /**
+     * Adds result groupings for each namespace in each package being queried for.
+     *
+     * <p>This method should be only called in query methods and get the READ lock to keep thread
+     * safety.
+     *
+     * @param resultSpecBuilder ResultSpecs as specified by client
+     * @param prefixes Prefixes that we should prepend to all our filters
+     * @param maxNumResults The maximum number of results for each grouping to support.
+     */
+    @GuardedBy("mReadWriteLock")
+    private void addPerPackagePerNamespaceResultGroupingsLocked(
+            @NonNull ResultSpecProto.Builder resultSpecBuilder,
+            @NonNull Set<String> prefixes,
+            int maxNumResults) {
+        Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet());
+        existingPrefixes.retainAll(prefixes);
+
+        // Create a map for package+namespace to prefixedNamespaces. This is NOT necessarily the
+        // same as the list of namespaces. If one package has multiple databases, each with the same
+        // namespace, then those should be grouped together.
+        Map<String, List<String>> packageAndNamespaceToNamespaces = new ArrayMap<>();
+        for (String prefix : existingPrefixes) {
+            Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
+            String packageName = getPackageName(prefix);
+            // Create a new prefix without the database name. This will allow us to group namespaces
+            // that have the same name and package but a different database name together.
+            String emptyDatabasePrefix = createPrefix(packageName, /*databaseName*/ "");
+            for (String prefixedNamespace : prefixedNamespaces) {
+                String namespace;
+                try {
+                    namespace = removePrefix(prefixedNamespace);
+                } catch (AppSearchException e) {
+                    // This should never happen. Skip this namespace if it does.
+                    Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed.");
+                    continue;
+                }
+                String emptyDatabasePrefixedNamespace = emptyDatabasePrefix + namespace;
+                List<String> namespaceList =
+                        packageAndNamespaceToNamespaces.get(emptyDatabasePrefixedNamespace);
+                if (namespaceList == null) {
+                    namespaceList = new ArrayList<>();
+                    packageAndNamespaceToNamespaces.put(
+                            emptyDatabasePrefixedNamespace, namespaceList);
+                }
+                namespaceList.add(prefixedNamespace);
+            }
+        }
+
+        for (List<String> namespaces : packageAndNamespaceToNamespaces.values()) {
+            resultSpecBuilder.addResultGroupings(
+                    ResultSpecProto.ResultGrouping.newBuilder()
+                            .addAllNamespaces(namespaces)
+                            .setMaxResults(maxNumResults));
+        }
+    }
+
+    /**
+     * Adds result groupings for each package being queried for.
+     *
+     * <p>This method should be only called in query methods and get the READ lock to keep thread
+     * safety.
+     *
+     * @param resultSpecBuilder ResultSpecs as specified by client
+     * @param prefixes Prefixes that we should prepend to all our filters
+     * @param maxNumResults The maximum number of results for each grouping to support.
+     */
+    @GuardedBy("mReadWriteLock")
+    private void addPerPackageResultGroupingsLocked(
+            @NonNull ResultSpecProto.Builder resultSpecBuilder,
+            @NonNull Set<String> prefixes,
+            int maxNumResults) {
+        Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet());
+        existingPrefixes.retainAll(prefixes);
+
+        // Build up a map of package to namespaces.
+        Map<String, List<String>> packageToNamespacesMap = new ArrayMap<>();
+        for (String prefix : existingPrefixes) {
+            Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
+            String packageName = getPackageName(prefix);
+            List<String> packageNamespaceList = packageToNamespacesMap.get(packageName);
+            if (packageNamespaceList == null) {
+                packageNamespaceList = new ArrayList<>();
+                packageToNamespacesMap.put(packageName, packageNamespaceList);
+            }
+            packageNamespaceList.addAll(prefixedNamespaces);
+        }
+
+        for (List<String> prefixedNamespaces : packageToNamespacesMap.values()) {
+            resultSpecBuilder.addResultGroupings(
+                    ResultSpecProto.ResultGrouping.newBuilder()
+                            .addAllNamespaces(prefixedNamespaces)
+                            .setMaxResults(maxNumResults));
+        }
+    }
+
+    /**
+     * Adds result groupings for each namespace being queried for.
+     *
+     * <p>This method should be only called in query methods and get the READ lock to keep thread
+     * safety.
+     *
+     * @param resultSpecBuilder ResultSpecs as specified by client
+     * @param prefixes Prefixes that we should prepend to all our filters
+     * @param maxNumResults The maximum number of results for each grouping to support.
+     */
+    @GuardedBy("mReadWriteLock")
+    private void addPerNamespaceResultGroupingsLocked(
+            @NonNull ResultSpecProto.Builder resultSpecBuilder,
+            @NonNull Set<String> prefixes,
+            int maxNumResults) {
+        Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet());
+        existingPrefixes.retainAll(prefixes);
+
+        // Create a map of namespace to prefixedNamespaces. This is NOT necessarily the
+        // same as the list of namespaces. If a namespace exists under different packages and/or
+        // different databases, they should still be grouped together.
+        Map<String, List<String>> namespaceToPrefixedNamespaces = new ArrayMap<>();
+        for (String prefix : existingPrefixes) {
+            Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
+            for (String prefixedNamespace : prefixedNamespaces) {
+                String namespace;
+                try {
+                    namespace = removePrefix(prefixedNamespace);
+                } catch (AppSearchException e) {
+                    // This should never happen. Skip this namespace if it does.
+                    Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed.");
+                    continue;
+                }
+                List<String> groupedPrefixedNamespaces =
+                        namespaceToPrefixedNamespaces.get(namespace);
+                if (groupedPrefixedNamespaces == null) {
+                    groupedPrefixedNamespaces = new ArrayList<>();
+                    namespaceToPrefixedNamespaces.put(namespace, groupedPrefixedNamespaces);
+                }
+                groupedPrefixedNamespaces.add(prefixedNamespace);
+            }
+        }
+
+        for (List<String> namespaces : namespaceToPrefixedNamespaces.values()) {
+            resultSpecBuilder.addResultGroupings(
+                    ResultSpecProto.ResultGrouping.newBuilder()
+                            .addAllNamespaces(namespaces)
+                            .setMaxResults(maxNumResults));
+        }
+    }
+
     @VisibleForTesting
     @GuardedBy("mReadWriteLock")
     SchemaProto getSchemaProtoLocked() throws AppSearchException {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
index ce1c9f4..800b073 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
@@ -46,12 +46,13 @@
      * SchemaTypeConfigProto}.
      */
     @NonNull
-    public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema) {
+    public static SchemaTypeConfigProto toSchemaTypeConfigProto(
+            @NonNull AppSearchSchema schema, int version) {
         Preconditions.checkNotNull(schema);
         SchemaTypeConfigProto.Builder protoBuilder =
                 SchemaTypeConfigProto.newBuilder()
                         .setSchemaType(schema.getSchemaType())
-                        .setVersion(schema.getVersion());
+                        .setVersion(version);
         List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
         for (int i = 0; i < properties.size(); i++) {
             PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i));
@@ -116,8 +117,7 @@
     @NonNull
     public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
         Preconditions.checkNotNull(proto);
-        AppSearchSchema.Builder builder =
-                new AppSearchSchema.Builder(proto.getSchemaType()).setVersion(proto.getVersion());
+        AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType());
         List<PropertyConfigProto> properties = proto.getPropertiesList();
         for (int i = 0; i < properties.size(); i++) {
             AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i));
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index 1d8db72..bf7e533 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -87,7 +87,9 @@
         GenericDocument document =
                 GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
         SearchResult.Builder builder =
-                new SearchResult.Builder(packageName, databaseName).setGenericDocument(document);
+                new SearchResult.Builder(packageName, databaseName)
+                        .setGenericDocument(document)
+                        .setRankingSignal(proto.getScore());
         if (proto.hasSnippet()) {
             for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
                 SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index 3b5e275..d9e8adb 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -104,6 +104,10 @@
                 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT;
             case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP:
                 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP;
+            case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT:
+                return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_COUNT;
+            case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP:
+                return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_LAST_USED_TIMESTAMP;
             default:
                 throw new IllegalArgumentException(
                         "Invalid result ranking strategy: " + rankingStrategyCode);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 0952215..58f430b 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I723a9d7b5e64329ab25b6d7627f3b2d222c31ac7
+Ie11a0555775a0ab2a39f6ce6d0d8a7b735c416ce
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index 9ef6e0b..bc30641 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -20,12 +20,12 @@
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.AppSearchSession;
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.BatchResultCallback;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByUriRequest;
 import android.app.appsearch.ReportUsageRequest;
@@ -34,6 +34,7 @@
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.StorageInfo;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
@@ -94,12 +95,20 @@
 
     @Override
     @NonNull
-    public ListenableFuture<Set<AppSearchSchema>> getSchema() {
-        SettableFuture<AppSearchResult<Set<AppSearchSchema>>> future = SettableFuture.create();
+    public ListenableFuture<GetSchemaResponse> getSchema() {
+        SettableFuture<AppSearchResult<GetSchemaResponse>> future = SettableFuture.create();
         mAppSearchSession.getSchema(mExecutor, future::set);
         return Futures.transformAsync(future, this::transformResult, mExecutor);
     }
 
+    @NonNull
+    @Override
+    public ListenableFuture<Set<String>> getNamespaces() {
+        SettableFuture<AppSearchResult<Set<String>>> future = SettableFuture.create();
+        mAppSearchSession.getNamespaces(mExecutor, future::set);
+        return Futures.transformAsync(future, this::transformResult, mExecutor);
+    }
+
     @Override
     @NonNull
     public ListenableFuture<AppSearchBatchResult<String, Void>> put(
@@ -154,6 +163,14 @@
         return Futures.transformAsync(future, this::transformResult, mExecutor);
     }
 
+    @NonNull
+    @Override
+    public ListenableFuture<StorageInfo> getStorageInfo() {
+        SettableFuture<AppSearchResult<StorageInfo>> future = SettableFuture.create();
+        mAppSearchSession.getStorageInfo(mExecutor, future::set);
+        return Futures.transformAsync(future, this::transformResult, mExecutor);
+    }
+
     @Override
     public void close() {
         mAppSearchSession.close();
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 69a4c18..5042ce0 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -21,9 +21,11 @@
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.GlobalSearchSession;
 import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.ReportSystemUsageRequest;
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
+import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -79,8 +81,24 @@
         return new SearchResultsShimImpl(searchResults, mExecutor);
     }
 
+    @NonNull
+    @Override
+    public ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request) {
+        SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
+        mGlobalSearchSession.reportSystemUsage(request, mExecutor, future::set);
+        return Futures.transformAsync(future, this::transformResult, mExecutor);
+    }
+
     @Override
     public void close() {
         mGlobalSearchSession.close();
     }
+
+    private <T> ListenableFuture<T> transformResult(
+            @NonNull AppSearchResult<T> result) throws AppSearchException {
+        if (!result.isSuccess()) {
+            throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
+        }
+        return Futures.immediateFuture(result.getResultValue());
+    }
 }
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 1428fb1..2069043 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
@@ -52,12 +52,20 @@
     /**
      * Retrieves the schema most recently successfully provided to {@link #setSchema}.
      *
-     * @return The pending result of performing this operation.
+     * @return The pending {@link GetSchemaResponse} of performing this operation.
      */
     // This call hits disk; async API prevents us from treating these calls as properties.
     @SuppressLint("KotlinPropertyAccess")
     @NonNull
-    ListenableFuture<Set<AppSearchSchema>> getSchema();
+    ListenableFuture<GetSchemaResponse> getSchema();
+
+    /**
+     * Retrieves the set of all namespaces in the current database with at least one document.
+     *
+     * @return The pending result of performing this operation.
+     */
+    @NonNull
+    ListenableFuture<Set<String>> getNamespaces();
 
     /**
      * Indexes documents into the {@link AppSearchSessionShim} database.
@@ -214,6 +222,17 @@
     ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /**
+     * Gets the storage info for this {@link AppSearchSessionShim} database.
+     *
+     * <p>This may take time proportional to the number of documents and may be inefficient to call
+     * repeatedly.
+     *
+     * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object.
+     */
+    @NonNull
+    ListenableFuture<StorageInfo> getStorageInfo();
+
+    /**
      * Flush all schema and document updates, additions, and deletes to disk if possible.
      *
      * @return The pending result of performing this operation. {@link
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 4a3c7a5..fd4734c 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
@@ -73,14 +73,22 @@
 
     public static List<GenericDocument> convertSearchResultsToDocuments(
             SearchResultsShim searchResults) throws Exception {
-        List<SearchResult> results = searchResults.getNextPage().get();
-        List<GenericDocument> documents = new ArrayList<>();
-        while (results.size() > 0) {
-            for (SearchResult result : results) {
-                documents.add(result.getGenericDocument());
-            }
-            results = searchResults.getNextPage().get();
+        List<SearchResult> results = retrieveAllSearchResults(searchResults);
+        List<GenericDocument> documents = new ArrayList<>(results.size());
+        for (SearchResult result : results) {
+            documents.add(result.getGenericDocument());
         }
         return documents;
     }
+
+    public static List<SearchResult> retrieveAllSearchResults(SearchResultsShim searchResults)
+            throws Exception {
+        List<SearchResult> page = searchResults.getNextPage().get();
+        List<SearchResult> results = new ArrayList<>();
+        while (!page.isEmpty()) {
+            results.addAll(page);
+            page = searchResults.getNextPage().get();
+        }
+        return results;
+    }
 }
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 440050f..f39916e 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
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.io.Closeable;
 
 /**
@@ -51,6 +53,26 @@
     @NonNull
     SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
+    /**
+     * Reports that a particular document has been used from a system surface.
+     *
+     * <p>See {@link AppSearchSessionShim#reportUsage} for a general description of document usage,
+     * as well as an API that can be used by the app itself.
+     *
+     * <p>Usage reported via this method is accounted separately from usage reported via {@link
+     * AppSearchSessionShim#reportUsage} and may be accessed using the constants {@link
+     * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and {@link
+     * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}.
+     *
+     * @return The pending result of performing this operation which resolves to {@code null} on
+     *     success. The pending result will be completed with an {@link
+     *     android.app.appsearch.exceptions.AppSearchException} with a code of {@link
+     *     AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an app which is not part
+     *     of the system.
+     */
+    @NonNull
+    ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request);
+
     /** Closes the {@link GlobalSearchSessionShim}. */
     @Override
     void close();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index baec0c3..5729385 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1497,7 +1497,7 @@
          * <ol>
          *     <li>Run as soon as possible</li>
          *     <li>Be less restricted during Doze and battery saver</li>
-         *     <li>Have the same network access as foreground services</li>
+         *     <li>Bypass Doze, app standby, and battery saver network restrictions</li>
          *     <li>Be less likely to be killed than regular jobs</li>
          *     <li>Be subject to background location throttling</li>
          * </ol>
@@ -1518,7 +1518,7 @@
          *
          * <p>
          * Assuming all constraints remain satisfied (including ideal system load conditions),
-         * expedited jobs are guaranteed to have a minimum allowed runtime of 1 minute. If your
+         * expedited jobs will have a maximum execution time of at least 1 minute. If your
          * app has remaining expedited job quota, then the expedited job <i>may</i> potentially run
          * longer until remaining quota is used up. Just like with regular jobs, quota is not
          * consumed while the app is on top and visible to the user.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 325be1b..d94d638 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -891,7 +891,7 @@
         }
 
         // Only expedited jobs can replace expedited jobs.
-        if (js.shouldTreatAsExpeditedJob()) {
+        if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
             // Keep fg/bg user distinction.
             if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
                 // Let any important bg user job replace a bg user expedited job.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a041f8c..8ac237e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2187,6 +2187,10 @@
      */
     @VisibleForTesting
     boolean isReadyToBeExecutedLocked(JobStatus job) {
+        return isReadyToBeExecutedLocked(job, true);
+    }
+
+    boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) {
         final boolean jobReady = job.isReady();
 
         if (DEBUG) {
@@ -2225,7 +2229,7 @@
         }
 
         final boolean jobPending = mPendingJobs.contains(job);
-        final boolean jobActive = isCurrentlyActiveLocked(job);
+        final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job);
 
         if (DEBUG) {
             Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index e8bcbfb..790fae0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -151,6 +151,14 @@
     /** The absolute maximum amount of time the job can run */
     private long mMaxExecutionTimeMillis;
 
+    /**
+     * The stop reason for a pending cancel. If there's not pending cancel, then the value should be
+     * {@link JobParameters#STOP_REASON_UNDEFINED}.
+     */
+    private int mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+    private int mPendingLegacyStopReason;
+    private String mPendingDebugStopReason;
+
     // Debugging: reason this job was last stopped.
     public String mStoppedReason;
 
@@ -328,6 +336,7 @@
             mAvailable = false;
             mStoppedReason = null;
             mStoppedTime = 0;
+            job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
             return true;
         }
     }
@@ -625,6 +634,19 @@
             }
             return;
         }
+        if (mRunningJob.startedAsExpeditedJob
+                && stopReasonCode == JobParameters.STOP_REASON_QUOTA) {
+            // EJs should be able to run for at least the min upper limit regardless of quota.
+            final long earliestStopTimeElapsed =
+                    mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
+            final long nowElapsed = sElapsedRealtimeClock.millis();
+            if (nowElapsed < earliestStopTimeElapsed) {
+                mPendingStopReason = stopReasonCode;
+                mPendingLegacyStopReason = legacyStopReason;
+                mPendingDebugStopReason = debugReason;
+                return;
+            }
+        }
         mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
         if (legacyStopReason == JobParameters.REASON_PREEMPT) {
             mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
@@ -777,6 +799,23 @@
                 closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
                 break;
             case VERB_EXECUTING:
+                if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+                    if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) {
+                        // Job became ready again while we were waiting to stop it (for example,
+                        // the device was temporarily taken off the charger). Ignore the pending
+                        // stop and see what the manager says.
+                        mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+                        mPendingLegacyStopReason = 0;
+                        mPendingDebugStopReason = null;
+                    } else {
+                        Slog.i(TAG, "JS was waiting to stop this job."
+                                + " Sending onStop: " + getRunningJobNameLocked());
+                        mParams.setStopReason(mPendingStopReason, mPendingLegacyStopReason,
+                                mPendingDebugStopReason);
+                        sendStopMessageLocked(mPendingDebugStopReason);
+                        break;
+                    }
+                }
                 final long latestStopTimeElapsed =
                         mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
                 final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -886,6 +925,9 @@
         mCancelled = false;
         service = null;
         mAvailable = true;
+        mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+        mPendingLegacyStopReason = 0;
+        mPendingDebugStopReason = null;
         removeOpTimeOutLocked();
         mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
@@ -972,7 +1014,16 @@
             pw.print(", ");
             TimeUtils.formatDuration(
                     (mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw);
-            pw.println("]");
+            pw.print("]");
+            if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+                pw.print(" Pending stop because ");
+                pw.print(mPendingStopReason);
+                pw.print("/");
+                pw.print(mPendingLegacyStopReason);
+                pw.print("/");
+                pw.print(mPendingDebugStopReason);
+            }
+            pw.println();
             pw.decreaseIndent();
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 6825cf2..df21d75 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -725,13 +725,6 @@
                     StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS,
                     mRequestedWhitelistJobs.keyAt(i));
         }
-        for (int i = 0; i < mAvailableNetworks.size(); i++) {
-            Network network = mAvailableNetworks.keyAt(i);
-            if (network != null) {
-                network.dumpDebug(proto,
-                        StateControllerProto.ConnectivityController.AVAILABLE_NETWORKS);
-            }
-        }
         for (int i = 0; i < mTrackedJobs.size(); i++) {
             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
             for (int j = 0; j < jobs.size(); j++) {
@@ -745,12 +738,6 @@
                         StateControllerProto.ConnectivityController.TrackedJob.INFO);
                 proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
                         js.getSourceUid());
-                NetworkRequest rn = js.getJob().getRequiredNetwork();
-                if (rn != null) {
-                    rn.dumpDebug(proto,
-                            StateControllerProto.ConnectivityController.TrackedJob
-                                    .REQUIRED_NETWORK);
-                }
                 proto.end(jsToken);
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 8fa0d3e..8d999e1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -328,6 +328,12 @@
     /** The evaluated priority of the job when it started running. */
     public int lastEvaluatedPriority;
 
+    /**
+     * Whether or not this particular JobStatus instance was treated as an EJ when it started
+     * running. This isn't copied over when a job is rescheduled.
+     */
+    public boolean startedAsExpeditedJob = false;
+
     // If non-null, this is work that has been enqueued for the job.
     public ArrayList<JobWorkItem> pendingWork;
 
@@ -1122,18 +1128,19 @@
      */
     public boolean canRunInDoze() {
         return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
-                || (shouldTreatAsExpeditedJob()
+                || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
                 && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
     }
 
     boolean canRunInBatterySaver() {
         return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
-                || (shouldTreatAsExpeditedJob()
+                || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
                 && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
     }
 
     boolean shouldIgnoreNetworkBlocking() {
-        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob();
+        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+                || (shouldTreatAsExpeditedJob() || startedAsExpeditedJob);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
@@ -2032,7 +2039,10 @@
         pw.println(serviceInfo != null);
         if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
             pw.print("readyWithinExpeditedQuota: ");
-            pw.println(mReadyWithinExpeditedQuota);
+            pw.print(mReadyWithinExpeditedQuota);
+            pw.print(" (started as EJ: ");
+            pw.print(startedAsExpeditedJob);
+            pw.println(")");
         }
         pw.decreaseIndent();
 
@@ -2171,9 +2181,6 @@
             if (uriPerms != null) {
                 uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
             }
-            if (job.getRequiredNetwork() != null) {
-                job.getRequiredNetwork().dumpDebug(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
-            }
             if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
                 proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES,
                         mTotalNetworkDownloadBytes);
@@ -2262,10 +2269,6 @@
             }
         }
 
-        if (network != null) {
-            network.dumpDebug(proto, JobStatusDumpProto.NETWORK);
-        }
-
         if (pendingWork != null) {
             for (int i = 0; i < pendingWork.size(); i++) {
                 dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 2b79969..91189e4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -849,10 +849,9 @@
             return true;
         }
         // A job is within quota if one of the following is true:
-        //   1. it's already running (already executing expedited jobs should be allowed to finish)
-        //   2. the app is currently in the foreground
-        //   3. the app overall is within its quota
-        //   4. It's on the temp allowlist (or within the grace period)
+        //   1. the app is currently in the foreground
+        //   2. the app overall is within its quota
+        //   3. It's on the temp allowlist (or within the grace period)
         if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) {
             return true;
         }
@@ -873,13 +872,6 @@
             return true;
         }
 
-        Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(),
-                jobStatus.getSourcePackageName());
-        // Any already executing expedited jobs should be allowed to finish.
-        if (ejTimer != null && ejTimer.isRunning(jobStatus)) {
-            return true;
-        }
-
         return 0 < getRemainingEJExecutionTimeLocked(
                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
     }
@@ -4153,6 +4145,8 @@
                 pw.print(", ");
                 if (js.shouldTreatAsExpeditedJob()) {
                     pw.print("within EJ quota");
+                } else if (js.startedAsExpeditedJob) {
+                    pw.print("out of EJ quota");
                 } else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
                     pw.print("within regular quota");
                 } else {
@@ -4163,6 +4157,8 @@
                     pw.print(getRemainingEJExecutionTimeLocked(
                             js.getSourceUserId(), js.getSourcePackageName()));
                     pw.print("ms remaining in EJ quota");
+                } else if (js.startedAsExpeditedJob) {
+                    pw.print("should be stopped after min execution time");
                 } else {
                     pw.print(getRemainingExecutionTimeLocked(js));
                     pw.print("ms remaining in quota");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 24f7b37..a62e258 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -190,7 +190,7 @@
                     ONE_HOUR,
                     ONE_HOUR,
                     2 * ONE_HOUR,
-                    4 * ONE_DAY
+                    4 * ONE_HOUR
             };
 
     private static final int[] THRESHOLD_BUCKETS = {
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 80698f7..bebf019 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -69,10 +69,12 @@
     method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException;
     method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...);
     method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer);
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
     method @NonNull public String getParserName();
     method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat);
     method public void release();
     method public void seek(@NonNull android.media.MediaParser.SeekPoint);
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
     method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object);
     method public boolean supportsParameter(@NonNull String);
     field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking";
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 8bdca76..cff422d 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.media.MediaCodec.CryptoInfo;
+import android.media.metrics.LogSessionId;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
@@ -74,6 +75,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Function;
@@ -1066,6 +1068,7 @@
     private boolean mReleased;
 
     // MediaMetrics fields.
+    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
     private final boolean mCreatedByName;
     private final SparseArray<Format> mTrackFormats;
     private String mLastObservedExceptionName;
@@ -1328,6 +1331,7 @@
                                 MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
 
         nativeSubmitMetrics(
+                // TODO: mLogSessionId,
                 mParserName,
                 mCreatedByName,
                 String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
@@ -1341,6 +1345,15 @@
                 videoHeight);
     }
 
+    public void setLogSessionId(@NonNull LogSessionId sessionId) {
+        this.mLogSessionId = Objects.requireNonNull(sessionId);
+    }
+
+    @NonNull
+    public LogSessionId getLogSessionId() {
+        return mLogSessionId;
+    }
+
     // Private methods.
 
     private MediaParser(
@@ -2184,6 +2197,7 @@
     // Native methods.
 
     private native void nativeSubmitMetrics(
+            // TODO: String logSessionId,
             String parserName,
             boolean createdByName,
             String parserPool,
diff --git a/core/api/current.txt b/core/api/current.txt
index bc74372..f428a56 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1179,6 +1179,7 @@
     field public static final int reqNavigation = 16843306; // 0x101022a
     field public static final int reqTouchScreen = 16843303; // 0x1010227
     field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
+    field public static final int requestOptimizedExternalStorageAccess = 16844357; // 0x1010645
     field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
@@ -6319,13 +6320,13 @@
     method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int);
     method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle);
     method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int);
-    method @NonNull public String getCreatorPackage();
+    method @Nullable public String getCreatorPackage();
     method public int getCreatorUid();
     method @NonNull public android.os.UserHandle getCreatorUserHandle();
     method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int);
     method @NonNull public android.content.IntentSender getIntentSender();
     method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int);
-    method @Deprecated @NonNull public String getTargetPackage();
+    method @Deprecated @Nullable public String getTargetPackage();
     method public boolean isActivity();
     method public boolean isBroadcast();
     method public boolean isForegroundService();
@@ -8999,6 +9000,7 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getName();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getType();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.os.ParcelUuid[] getUuids();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean);
     method public boolean setPin(byte[]);
     method public void writeToParcel(android.os.Parcel, int);
@@ -20577,6 +20579,7 @@
     method public int getChannelConfiguration();
     method public int getChannelCount();
     method @NonNull public android.media.AudioFormat getFormat();
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
     method public android.os.PersistableBundle getMetrics();
     method public static int getMinBufferSize(int, int, int);
     method public int getNotificationMarkerPosition();
@@ -20599,6 +20602,7 @@
     method public void release();
     method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
     method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
     method public int setNotificationMarkerPosition(int);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -20714,6 +20718,7 @@
     method public int getChannelCount();
     method public int getDualMonoMode();
     method @NonNull public android.media.AudioFormat getFormat();
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
     method public static float getMaxVolume();
     method public android.os.PersistableBundle getMetrics();
     method public static int getMinBufferSize(int, int, int);
@@ -20751,6 +20756,7 @@
     method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float);
     method public int setBufferSizeInFrames(@IntRange(from=0) int);
     method public boolean setDualMonoMode(int);
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
     method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
     method public int setNotificationMarkerPosition(int);
     method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int);
@@ -21305,7 +21311,7 @@
     method @NonNull public String getDiagnosticInfo();
   }
 
-  public final class MediaCodec implements android.media.metrics.PlaybackComponent {
+  public final class MediaCodec {
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
     method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
     method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -21331,7 +21337,6 @@
     method @NonNull public android.media.MediaFormat getOutputFormat(int);
     method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
     method @Nullable public android.media.Image getOutputImage(int);
-    method public String getPlaybackId();
     method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
     method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
@@ -21348,7 +21353,6 @@
     method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
     method public void setOutputSurface(@NonNull android.view.Surface);
     method public void setParameters(@Nullable android.os.Bundle);
-    method public void setPlaybackId(@NonNull String);
     method public void setVideoScalingMode(int);
     method public void signalEndOfInputStream();
     method public void start();
@@ -21962,7 +21966,7 @@
     method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds();
     method public int getOfflineLicenseState(@NonNull byte[]);
     method public int getOpenSessionCount();
-    method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
+    method @Nullable public android.media.MediaDrm.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
     method @NonNull public byte[] getPropertyByteArray(String);
     method @NonNull public String getPropertyString(@NonNull String);
     method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -22167,6 +22171,11 @@
     method public void onSessionLostState(@NonNull android.media.MediaDrm, @NonNull byte[]);
   }
 
+  public final class MediaDrm.PlaybackComponent {
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
+  }
+
   public static final class MediaDrm.ProvisionRequest {
     method @NonNull public byte[] getData();
     method @NonNull public String getDefaultUrl();
@@ -22199,6 +22208,7 @@
     method public long getCachedDuration();
     method public android.media.MediaExtractor.CasInfo getCasInfo(int);
     method public android.media.DrmInitData getDrmInitData();
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
     method public android.os.PersistableBundle getMetrics();
     method @Nullable public java.util.Map<java.util.UUID,byte[]> getPsshInfo();
     method public boolean getSampleCryptoInfo(@NonNull android.media.MediaCodec.CryptoInfo);
@@ -22220,6 +22230,7 @@
     method public void setDataSource(@NonNull android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
     method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
     method public void setMediaCas(@NonNull android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
@@ -22823,6 +22834,7 @@
     method public java.util.List<android.media.MicrophoneInfo> getActiveMicrophones() throws java.io.IOException;
     method @Nullable public android.media.AudioRecordingConfiguration getActiveRecordingConfiguration();
     method public static final int getAudioSourceMax();
+    method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
     method public int getMaxAmplitude() throws java.lang.IllegalStateException;
     method public android.os.PersistableBundle getMetrics();
     method public android.media.AudioDeviceInfo getPreferredDevice();
@@ -22845,6 +22857,7 @@
     method public void setCaptureRate(double);
     method public void setInputSurface(@NonNull android.view.Surface);
     method public void setLocation(float, float);
+    method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
     method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
     method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
     method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException;
@@ -24476,6 +24489,8 @@
   }
 
   public final class LogSessionId {
+    method @NonNull public String getStringId();
+    field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE;
   }
 
   public class MediaMetricsManager {
@@ -24509,11 +24524,6 @@
     method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
   }
 
-  public interface PlaybackComponent {
-    method @NonNull public String getPlaybackId();
-    method public void setPlaybackId(@NonNull String);
-  }
-
   public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable {
     method public int describeContents();
     method public int getErrorCode();
@@ -26965,7 +26975,7 @@
   public abstract static class VcnManager.VcnStatusCallback {
     ctor public VcnManager.VcnStatusCallback();
     method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable);
-    method public abstract void onVcnStatusChanged(int);
+    method public abstract void onStatusChanged(int);
   }
 
 }
@@ -34917,7 +34927,7 @@
     field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
     field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
     field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
-    field public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS = "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
     field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
@@ -35548,6 +35558,9 @@
     method public static android.net.Uri getUriForSubscriptionIdAndField(int, String);
     field public static final String AUTHORITY = "service-state";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final String DATA_NETWORK_TYPE = "data_network_type";
+    field public static final String DATA_REG_STATE = "data_reg_state";
+    field public static final String DUPLEX_MODE = "duplex_mode";
     field public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
     field public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
     field public static final String VOICE_REG_STATE = "voice_reg_state";
@@ -40547,6 +40560,7 @@
     field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
     field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
     field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
+    field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool";
     field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
     field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
     field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -40571,6 +40585,7 @@
     field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
+    field public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL = "hide_no_calling_indicator_on_data_network_bool";
     field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool";
     field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -40736,6 +40751,7 @@
     field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int";
     field public static final String KEY_PREFIX = "ims.";
     field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool";
+    field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array";
     field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
   }
 
@@ -42052,7 +42068,6 @@
     method public static int[] calculateLength(String, boolean);
     method @Deprecated public static android.telephony.SmsMessage createFromPdu(byte[]);
     method public static android.telephony.SmsMessage createFromPdu(byte[], String);
-    method @Nullable public static android.telephony.SmsMessage createSmsSubmitPdu(@NonNull byte[], boolean);
     method public String getDisplayMessageBody();
     method public String getDisplayOriginatingAddress();
     method public String getEmailBody();
@@ -42153,6 +42168,7 @@
     method public static int getDefaultSmsSubscriptionId();
     method public static int getDefaultSubscriptionId();
     method public static int getDefaultVoiceSubscriptionId();
+    method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
     method public int getDeviceToDeviceStatusSharingPreference(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
     method public static int getSlotIndex(int);
@@ -42166,6 +42182,7 @@
     method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(@NonNull java.util.List<android.net.Uri>, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
     method public void setSubscriptionOverrideCongested(int, boolean, long);
@@ -42181,8 +42198,9 @@
     field public static final int D2D_SHARING_ALL = 3; // 0x3
     field public static final int D2D_SHARING_ALL_CONTACTS = 1; // 0x1
     field public static final int D2D_SHARING_DISABLED = 0; // 0x0
-    field public static final int D2D_SHARING_STARRED_CONTACTS = 2; // 0x2
+    field public static final int D2D_SHARING_SELECTED_CONTACTS = 2; // 0x2
     field public static final String D2D_STATUS_SHARING = "d2d_sharing_status";
+    field public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS = "d2d_sharing_contacts";
     field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
     field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
     field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff
@@ -42338,7 +42356,7 @@
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
     field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
-    field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5; // 0x5
     field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
     field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
   }
@@ -42500,6 +42518,7 @@
     field public static final int CALL_STATE_IDLE = 0; // 0x0
     field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
     field public static final int CALL_STATE_RINGING = 1; // 0x1
+    field public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED = "CAPABILITY_SLICING_CONFIG_SUPPORTED";
     field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
     field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
     field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
@@ -48089,6 +48108,7 @@
   }
 
   public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
+    ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
     method public int describeContents();
     method public void release();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -52568,6 +52588,7 @@
     method public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
     method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext);
     method @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int);
+    method @Nullable public android.app.PendingIntent getTranslationSettingsActivityIntent();
     method public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
   }
 
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e3b7c88..8dc5c15 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -312,6 +312,10 @@
     field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0
   }
 
+  public final class StorageVolume implements android.os.Parcelable {
+    method @NonNull public android.os.UserHandle getOwner();
+  }
+
 }
 
 package android.provider {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a0e0f71..55dd8b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -267,6 +267,7 @@
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+    field public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
@@ -1837,7 +1838,7 @@
 
 package android.apphibernation {
 
-  public final class AppHibernationManager {
+  public class AppHibernationManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
@@ -2177,6 +2178,7 @@
 package android.companion {
 
   public final class CompanionDeviceManager {
+    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, int);
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
   }
 
@@ -2443,6 +2445,7 @@
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method @Nullable public Boolean hasRequestOptimizedExternalStorageAccess();
     method public boolean isEncryptionAware();
     method public boolean isInstantApp();
     method public boolean isOem();
@@ -2872,14 +2875,11 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
     method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @CheckResult @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @CheckResult @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
     field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1
-    field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2
-    field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3
-    field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6
-    field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5
-    field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4
+    field public static final int ERROR_UNABLE_TO_APPROVE = 3; // 0x3
+    field public static final int ERROR_UNKNOWN_DOMAIN = 2; // 0x2
     field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
     field public static final int STATUS_OK = 0; // 0x0
   }
@@ -4450,6 +4450,7 @@
     method public boolean hasLowPowerMode();
     method public boolean hasMeasurementCorrections();
     method public boolean hasMeasurementCorrectionsExcessPathLength();
+    method public boolean hasMeasurementCorrectionsForDriving();
     method public boolean hasMeasurementCorrectionsLosSats();
     method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane();
     method public boolean hasMeasurementCorrectionsReflectingPlane();
@@ -4465,6 +4466,7 @@
     method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrections(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsExcessPathLength(boolean);
+    method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsForDriving(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsLosSats(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsReflectingPlane(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrelationVectors(boolean);
@@ -5433,6 +5435,8 @@
   public final class MediaSessionManager {
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.media.session.MediaSession.Token getMediaKeyEventSession();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public String getMediaKeyEventSessionPackageName();
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
     method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
@@ -7781,6 +7785,7 @@
     method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
     method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
+    method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
     method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback);
     method public void setOnServiceDeadCallback(@NonNull Runnable);
     method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback);
@@ -7793,6 +7798,7 @@
     method public boolean tearDownClientInterface(@NonNull String);
     method public boolean tearDownInterfaces();
     method public boolean tearDownSoftApInterface(@NonNull String);
+    method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
     field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
     field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
     field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
@@ -7803,6 +7809,10 @@
     field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
   }
 
+  public static interface WifiNl80211Manager.CountryCodeChangeListener {
+    method public void onChanged(@NonNull String);
+  }
+
   public static class WifiNl80211Manager.OemSecurityType {
     ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
     field public final int groupCipher;
@@ -8677,9 +8687,15 @@
     method @WorkerThread public void allocateBytes(@NonNull java.util.UUID, long, @RequiresPermission int) throws java.io.IOException;
     method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
     method public static boolean hasIsolatedStorage();
     method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
+    field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
+    field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
+    field public static final int MOUNT_MODE_EXTERNAL_INSTALLER = 2; // 0x2
+    field public static final int MOUNT_MODE_EXTERNAL_NONE = 0; // 0x0
+    field public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = 3; // 0x3
     field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2
     field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1
     field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0
@@ -8926,6 +8942,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
+    field public static final String NAMESPACE_APPSEARCH = "appsearch";
     field public static final String NAMESPACE_APP_COMPAT = "app_compat";
     field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
     field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
@@ -9003,6 +9020,8 @@
     method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri);
     field public static final String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
     field public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
+    field public static final String DOWNLOADS_PROVIDER_AUTHORITY = "downloads";
+    field public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
     field public static final String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED";
   }
 
@@ -10331,7 +10350,6 @@
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
     field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
     field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
-    field public static final int HOTWORD_DETECTION_FALSE_ALERT = 0; // 0x0
     field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
@@ -10354,7 +10372,7 @@
     method public abstract void onError();
     method public abstract void onRecognitionPaused();
     method public abstract void onRecognitionResumed();
-    method public void onRejected(int);
+    method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
   }
 
   public static class AlwaysOnHotwordDetector.EventPayload {
@@ -10397,13 +10415,13 @@
     ctor public HotwordDetectionService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onDetectFromDspSource(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.DspHotwordDetectionCallback);
-    method public void onUpdateState(@Nullable android.os.Bundle, @Nullable android.os.SharedMemory);
+    method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
     field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService";
   }
 
   public static final class HotwordDetectionService.DspHotwordDetectionCallback {
     method public void onDetected();
-    method public void onRejected();
+    method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
   }
 
   public interface HotwordDetector {
@@ -10422,7 +10440,7 @@
 
   public class VoiceInteractionService extends android.app.Service {
     method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
   }
 
@@ -11262,12 +11280,11 @@
 
   public final class PhoneCapability implements android.os.Parcelable {
     method public int describeContents();
-    method public int getDeviceNrCapabilityBitmask();
-    method @IntRange(from=1) public int getMaxActiveInternetData();
-    method @IntRange(from=1) public int getMaxActivePacketSwitchedVoiceCalls();
+    method @NonNull public int[] getDeviceNrCapabilities();
+    method @IntRange(from=1) public int getMaxActiveDataSubscriptions();
+    method @IntRange(from=1) public int getMaxActiveVoiceSubscriptions();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR;
-    field public static final int DEVICE_NR_CAPABILITY_NONE = 0; // 0x0
     field public static final int DEVICE_NR_CAPABILITY_NSA = 1; // 0x1
     field public static final int DEVICE_NR_CAPABILITY_SA = 2; // 0x2
   }
@@ -14280,13 +14297,9 @@
   }
 
   public final class UiTranslationManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId);
   }
 
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 0c02c43..3926e39 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -181,3 +181,14 @@
 
 }
 
+package android.view.translation {
+
+  public final class UiTranslationManager {
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int);
+  }
+
+}
+
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7bc1993..746a0b6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -34,6 +34,7 @@
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+    field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
     field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
@@ -1477,7 +1478,7 @@
 package android.media.metrics {
 
   public final class LogSessionId {
-    method @NonNull public String getStringId();
+    ctor public LogSessionId(@NonNull String);
   }
 
 }
@@ -1703,6 +1704,7 @@
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
     method public abstract long getDuration();
+    method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
     field public static final int EFFECT_POP = 4; // 0x4
     field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
     field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
@@ -1724,6 +1726,20 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
   }
 
+  public static final class VibrationEffect.Composition {
+    method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
+    method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int);
+  }
+
+  public static final class VibrationEffect.WaveformBuilder {
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int);
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+    method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int);
+    method @NonNull public android.os.VibrationEffect build();
+    method @NonNull public android.os.VibrationEffect build(int);
+  }
+
   public class VintfObject {
     method public static String[] getHalNamesAndVersions();
     method public static String getSepolicyVersion();
@@ -1822,6 +1838,7 @@
   public class StorageManager {
     method @NonNull public static java.util.UUID convert(@NonNull String);
     method @NonNull public static String convert(@NonNull java.util.UUID);
+    method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
     method public static boolean isUserKeyUnlocked(int);
   }
 
@@ -1870,11 +1887,28 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
   }
 
+  public final class RampSegment extends android.os.vibrator.VibrationEffectSegment {
+    method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int);
+    method public int describeContents();
+    method public long getDuration();
+    method public float getEndAmplitude();
+    method public float getEndFrequency();
+    method public float getStartAmplitude();
+    method public float getStartFrequency();
+    method public boolean hasNonZeroAmplitude();
+    method @NonNull public android.os.vibrator.RampSegment resolve(int);
+    method @NonNull public android.os.vibrator.RampSegment scale(float);
+    method public void validate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR;
+  }
+
   public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
     method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
     method public int describeContents();
     method public float getAmplitude();
     method public long getDuration();
+    method public float getFrequency();
     method public boolean hasNonZeroAmplitude();
     method @NonNull public android.os.vibrator.StepSegment resolve(int);
     method @NonNull public android.os.vibrator.StepSegment scale(float);
@@ -2299,6 +2333,7 @@
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
     method public int getDataNetworkType();
+    method public int getDataRegState();
     method public void setCdmaSystemAndNetworkId(int, int);
     method public void setCellBandwidths(int[]);
     method public void setChannelNumber(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c1d8541..7298d87 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2328,7 +2328,7 @@
     }
 
     @UnsupportedAppUsage
-    final Handler getHandler() {
+    public Handler getHandler() {
         return mH;
     }
 
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0358fe5..03e95fc 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,10 +19,12 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.StrictMode.vmIncorrectContextUseEnabled;
+import static android.view.WindowManager.LayoutParams.WindowType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -87,6 +89,8 @@
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.autofill.AutofillManager.AutofillClient;
+import android.window.WindowContext;
+import android.window.WindowTokenClient;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -1995,8 +1999,8 @@
                 final String errorMessage = "Tried to access visual service "
                         + SystemServiceRegistry.getSystemServiceClassName(name)
                         + " from a non-visual Context:" + getOuterContext();
-                final String message = "Visual services, such as WindowManager"
-                        + "or LayoutInflater should be accessed from Activity or other visual "
+                final String message = "Visual services, such as WindowManager "
+                        + "or LayoutInflater should be accessed from Activity or another visual "
                         + "Context. Use an Activity or a Context created with "
                         + "Context#createWindowContext(int, Bundle), which are adjusted to "
                         + "the configuration and visual bounds of an area on screen.";
@@ -2563,23 +2567,63 @@
 
     @NonNull
     @Override
-    public WindowContext createWindowContext(int type, @NonNull Bundle options) {
+    public WindowContext createWindowContext(@WindowType int type,
+            @Nullable Bundle options) {
         if (getDisplay() == null) {
-            throw new UnsupportedOperationException("WindowContext can only be created from "
-                    + "other visual contexts, such as Activity or one created with "
-                    + "Context#createDisplayContext(Display)");
+            throw new UnsupportedOperationException("Please call this API with context associated"
+                    + " with a display instance, such as Activity or context created via"
+                    + " Context#createDisplayContext(Display), or try to invoke"
+                    + " Context#createWindowContext(Display, int, Bundle)");
         }
-        return new WindowContext(this, type, options);
+        return createWindowContextInternal(getDisplay(), type, options);
     }
 
     @NonNull
     @Override
-    public WindowContext createWindowContext(@NonNull Display display, int type,
-            @NonNull Bundle options) {
+    public WindowContext createWindowContext(@NonNull Display display, @WindowType int type,
+            @Nullable Bundle options) {
         if (display == null) {
             throw new IllegalArgumentException("Display must not be null");
         }
-        return new WindowContext(this, display, type, options);
+        return createWindowContextInternal(display, type, options);
+    }
+
+    /**
+     * The internal implementation of {@link Context#createWindowContext(int, Bundle)} and
+     * {@link Context#createWindowContext(Display, int, Bundle)}.
+     *
+     * @param display The {@link Display} instance to be associated with.
+     *
+     * @see Context#createWindowContext(Display, int, Bundle)
+     * @see Context#createWindowContext(int, Bundle)
+     */
+    private WindowContext createWindowContextInternal(@NonNull Display display,
+            @WindowType int type, @Nullable Bundle options) {
+        // Step 1. Create a WindowTokenClient to associate with the WindowContext's Resources
+        //         instance and it will be later used to receive configuration updates from the
+        //         server side.
+        final WindowTokenClient windowTokenClient = new WindowTokenClient();
+
+        // Step 2. Create the base context of the window context, it will also create a Resources
+        //         associated with the WindowTokenClient and set the token to the base context.
+        final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display);
+
+        // Step 3. Create a WindowContext instance and set it as the outer context of the base
+        //         context to make the service obtained by #getSystemService(String) able to query
+        //         the WindowContext's WindowManager instead of the default one.
+        final WindowContext windowContext = new WindowContext(windowContextBase, type, options);
+        windowContextBase.setOuterContext(windowContext);
+
+        // Step 4. Attach the WindowContext to the WindowTokenClient. In this way, when there's a
+        //         configuration update from the server side, the update will then apply to
+        //         WindowContext's resources.
+        windowTokenClient.attachContext(windowContext);
+
+        // Step 5. Register the window context's token to the server side to associate with a
+        //         window manager node.
+        windowContext.registerWithServer();
+
+        return windowContext;
     }
 
     @NonNull
@@ -2588,40 +2632,65 @@
         if (display == null) {
             throw new IllegalArgumentException("Display must not be null");
         }
-        final ContextImpl tokenContext = createBaseWindowContext(token, display);
-        tokenContext.setResources(createWindowContextResources());
+        final ContextImpl tokenContext = createWindowContextBase(token, display);
+        tokenContext.setResources(createWindowContextResources(tokenContext));
         return tokenContext;
     }
 
-
-    ContextImpl createBaseWindowContext(IBinder token, Display display) {
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+    /**
+     * Creates the base {@link Context} for UI context to associate with a non-{@link Activity}
+     * window.
+     *
+     * @param token The token to associate with {@link Resources}
+     * @param display The {@link Display} to associate with.
+     *
+     * @see #createWindowContext(Display, int, Bundle)
+     * @see #createTokenContext(IBinder, Display)
+     */
+    @UiContext
+    ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) {
+        ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
                 mSplitName, token, mUser, mFlags, mClassLoader, null);
         // Window contexts receive configurations directly from the server and as such do not
         // need to override their display in ResourcesManager.
-        context.mForceDisplayOverrideInResources = false;
-        context.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
-        if (display != null) {
-            context.mDisplay = display;
-        }
-        return context;
+        baseContext.mForceDisplayOverrideInResources = false;
+        baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
+        baseContext.mDisplay = display;
+
+        final Resources windowContextResources = createWindowContextResources(baseContext);
+        baseContext.setResources(windowContextResources);
+
+        return baseContext;
     }
 
-    Resources createWindowContextResources() {
-        final String resDir = mPackageInfo.getResDir();
-        final String[] splitResDirs = mPackageInfo.getSplitResDirs();
-        final String[] legacyOverlayDirs = mPackageInfo.getOverlayDirs();
-        final String[] overlayPaths = mPackageInfo.getOverlayPaths();
-        final String[] libDirs = mPackageInfo.getApplicationInfo().sharedLibraryFiles;
-        final int displayId = getDisplayId();
-        final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
-                ? mPackageInfo.getCompatibilityInfo()
-                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-        final List<ResourcesLoader> loaders = mResources.getLoaders();
+    /**
+     * Creates the {@link Resources} to associate with the {@link WindowContext}'s token.
+     *
+     * When there's a {@link Configuration} update, this Resources instance will be updated to match
+     * the new configuration.
+     *
+     * @see WindowTokenClient
+     * @see #getWindowContextToken()
+     */
+    private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) {
+        final LoadedApk packageInfo = windowContextBase.mPackageInfo;
+        final ClassLoader classLoader = windowContextBase.mClassLoader;
+        final IBinder token = windowContextBase.getWindowContextToken();
 
-        return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs,
-                legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */,
-                compatInfo, mClassLoader, loaders);
+        final String resDir = packageInfo.getResDir();
+        final String[] splitResDirs = packageInfo.getSplitResDirs();
+        final String[] legacyOverlayDirs = packageInfo.getOverlayDirs();
+        final String[] overlayPaths = packageInfo.getOverlayPaths();
+        final String[] libDirs = packageInfo.getApplicationInfo().sharedLibraryFiles;
+        final int displayId = windowContextBase.getDisplayId();
+        final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
+                ? packageInfo.getCompatibilityInfo()
+                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        final List<ResourcesLoader> loaders = windowContextBase.mResources.getLoaders();
+
+        return windowContextBase.mResourcesManager.createBaseTokenResources(token, resDir,
+                splitResDirs, legacyOverlayDirs, overlayPaths, libDirs, displayId,
+                null /* overrideConfig */, compatInfo, classLoader, loaders);
     }
 
     @NonNull
@@ -3114,6 +3183,14 @@
         return result;
     }
 
+    @Override
+    public void destroy() {
+        // The final clean-up is to release BroadcastReceiver registrations. It is called in
+        // ActivityThread for Activity and Service. For the context, such as WindowContext,
+        // without lifecycle concept, it should be called once the context is released.
+        scheduleFinalCleanup(getClass().getName(), getOuterContext().getClass().getSimpleName());
+    }
+
     // ----------------------------------------------------------------------
     // ----------------------------------------------------------------------
     // ----------------------------------------------------------------------
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4eda6fe..420ec08 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -39,7 +39,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.LocusId;
@@ -5217,8 +5216,7 @@
                     R.dimen.notification_right_icon_size) / density;
             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
             if (largeIconShown && (p.mPromotePicture
-                    || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
-                    || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
+                    || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
                 Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
                 if (drawable != null) {
                     int iconWidth = drawable.getIntrinsicWidth();
@@ -5285,8 +5283,7 @@
             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
             // Use different highlighted colors except when low-priority mode prevents that
             if (!p.mReduceHighlights) {
-                textColor = getBackgroundColor(p);
-                pillColor = getAccentColor(p);
+                pillColor = getAccentTertiaryColor(p);
             }
             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -5662,55 +5659,45 @@
             if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
                 return false;
             }
-            final ContentResolver contentResolver = mContext.getContentResolver();
-            final int decorationType = DevFlags.getFullyCustomViewNotifDecoration(contentResolver);
-            return decorationType != DevFlags.DECORATION_NONE
-                    && (DevFlags.shouldBackportSNotifRules(contentResolver)
-                    || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S);
+            return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
         }
 
         private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
-            int decorationType =
-                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
             StandardTemplateParams p = mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
                     .fillTextsFrom(this);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
-                    p, result, decorationType);
+                    p, result);
             return standard;
         }
 
         private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
-            int decorationType =
-                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
             StandardTemplateParams p = mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
                     .fillTextsFrom(this);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
                     p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
-                    p, result, decorationType);
+                    p, result);
             return standard;
         }
 
         private RemoteViews minimallyDecoratedHeadsUpContentView(
                 @NonNull RemoteViews customContent) {
-            int decorationType =
-                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
             StandardTemplateParams p = mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
                     .fillTextsFrom(this);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
                     p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
-                    p, result, decorationType);
+                    p, result);
             return standard;
         }
 
@@ -5769,12 +5756,6 @@
                             .fillTextsFrom(this);
                     result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
                             null /* result */);
-                } else if (DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())
-                        && useExistingRemoteView()
-                        && fullyCustomViewRequiresDecoration(false /* fromStyle */)) {
-                    // This "backport" logic is a special case to handle the UNDO style of notif
-                    // so that we can see what that will look like when the app targets S.
-                    result = minimallyDecoratedBigContentView(mN.contentView);
                 }
             }
             makeHeaderExpanded(result);
@@ -6237,6 +6218,25 @@
         }
 
         /**
+         * Gets the tertiary accent color for colored UI elements. If we're tinting with the theme
+         * accent, this comes from the tertiary system accent palette, otherwise this would be
+         * identical to {@link #getSmallIconColor(StandardTemplateParams)}.
+         */
+        private @ColorInt int getAccentTertiaryColor(StandardTemplateParams p) {
+            if (isColorized(p)) {
+                return getPrimaryTextColor(p);
+            }
+            if (mTintWithThemeAccent) {
+                int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary,
+                        COLOR_INVALID);
+                if (color != COLOR_INVALID) {
+                    return color;
+                }
+            }
+            return getContrastColor(p);
+        }
+
+        /**
          * Gets the theme's error color, or the primary text color for colorized notifications.
          */
         private @ColorInt int getErrorColor(StandardTemplateParams p) {
@@ -6864,24 +6864,18 @@
 
     private static void buildCustomContentIntoTemplate(@NonNull Context context,
             @NonNull RemoteViews template, @Nullable RemoteViews customContent,
-            @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result,
-            int decorationType) {
+            @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
         int childIndex = -1;
         if (customContent != null) {
             // Need to clone customContent before adding, because otherwise it can no longer be
             // parceled independently of remoteViews.
             customContent = customContent.clone();
             if (p.mHeaderless) {
-                if (decorationType <= DevFlags.DECORATION_PARTIAL) {
-                    template.removeFromParent(R.id.notification_top_line);
-                }
-                // The vertical margins are bigger in the "two-line" scenario than the "one-line"
-                //  scenario, but the 'compatible' decoration state is intended to have 3 lines,
-                //  (1 for the top line views and 2 for the custom views), so in that one case we
-                //  use the smaller 1-line margins. This gives the compatible case 88-16*2=56 dp of
-                //  height, 24dp of which goes to the top line, leaving 32dp for the custom view.
-                boolean hasSecondLine = decorationType != DevFlags.DECORATION_FULL_COMPATIBLE;
-                Builder.setHeaderlessVerticalMargins(template, p, hasSecondLine);
+                template.removeFromParent(R.id.notification_top_line);
+                // We do not know how many lines ar emote view has, so we presume it has 2;  this
+                // ensures that we don't under-pad the content, which could lead to abuse, at the
+                // cost of making single-line custom content over-padded.
+                Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
             } else {
                 // also update the end margin to account for the large icon or expander
                 Resources resources = context.getResources();
@@ -9042,8 +9036,7 @@
             template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
 
             // Add custom view if provided by subclass.
-            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
-                    DevFlags.DECORATION_PARTIAL);
+            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
             return template;
         }
 
@@ -9066,8 +9059,7 @@
                     template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
                 }
             }
-            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
-                    DevFlags.DECORATION_PARTIAL);
+            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
             return template;
         }
     }
@@ -9644,16 +9636,15 @@
             if (mBuilder.mActions.size() == 0) {
                return makeStandardTemplateWithCustomContent(headsUpContentView);
             }
-            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
-                    p, result, decorationType);
+                    p, result);
             return remoteViews;
         }
 
@@ -9661,16 +9652,15 @@
             if (customContent == null) {
                 return null;  // no custom view; use the default behavior
             }
-            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
                     mBuilder.getBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
-                    p, result, decorationType);
+                    p, result);
             return remoteViews;
         }
 
@@ -9681,24 +9671,18 @@
             if (bigContentView == null) {
                 return null;  // no custom view; use the default behavior
             }
-            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
-                    .decorationType(decorationType)
+                    .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getBigBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
-                    p, result, decorationType);
+                    p, result);
             return remoteViews;
         }
 
-        private int getDecorationType() {
-            ContentResolver contentResolver = mBuilder.mContext.getContentResolver();
-            return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver);
-        }
-
         /**
          * @hide
          */
@@ -12091,6 +12075,22 @@
     }
 
     private static class StandardTemplateParams {
+        /**
+         * Notifications will be minimally decorated with ONLY an icon and expander:
+         * <li>A large icon is never shown.
+         * <li>A progress bar is never shown.
+         * <li>The expanded and heads up states do not show actions, even if provided.
+         */
+        public static final int DECORATION_MINIMAL = 1;
+
+        /**
+         * Notifications will be partially decorated with AT LEAST an icon and expander:
+         * <li>A large icon is shown if provided.
+         * <li>A progress bar is shown if provided and enough space remains below the content.
+         * <li>Actions are shown in the expanded and heads up states.
+         */
+        public static final int DECORATION_PARTIAL = 2;
+
         public static int VIEW_TYPE_UNSPECIFIED = 0;
         public static int VIEW_TYPE_NORMAL = 1;
         public static int VIEW_TYPE_BIG = 2;
@@ -12276,112 +12276,20 @@
         }
 
         public StandardTemplateParams decorationType(int decorationType) {
-            boolean hideTitle = decorationType <= DevFlags.DECORATION_FULL_COMPATIBLE;
-            boolean hideOtherFields = decorationType <= DevFlags.DECORATION_MINIMAL;
-            hideTitle(hideTitle);
+            // These fields are removed by the decoration process, and thus would not show anyway;
+            // hiding them is a minimal time/space optimization.
+            hideAppName(true);
+            hideTitle(true);
+            hideSubText(true);
+            hideTime(true);
+            // Minimally decorated custom views do not show certain pieces of chrome that have
+            // always been shown when using DecoratedCustomViewStyle.
+            boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
             hideLargeIcon(hideOtherFields);
             hideProgress(hideOtherFields);
             hideActions(hideOtherFields);
+            hideSnoozeButton(hideOtherFields);
             return this;
         }
     }
-
-    /**
-     * A class to centrally access various developer flags related to notifications.
-     * This class is a non-final wrapper around Settings.Global which allows mocking for unit tests.
-     * TODO(b/176239013): Try to remove this before shipping S
-     * @hide
-     */
-    public static class DevFlags {
-
-        /**
-         * Notifications will not be decorated.  The custom content will be shown as-is.
-         *
-         * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
-         * as that API contract includes decorations that this does not provide.
-         */
-        public static final int DECORATION_NONE = 0;
-
-        /**
-         * Notifications will be minimally decorated with ONLY an icon and expander as follows:
-         * <li>A large icon is never shown.
-         * <li>A progress bar is never shown.
-         * <li>The expanded and heads up states do not show actions, even if provided.
-         * <li>The collapsed state gives the app's custom content 48dp of vertical space.
-         * <li>The collapsed state does NOT include the top line of views,
-         * like the alerted icon or work profile badge.
-         *
-         * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
-         * as that API contract includes decorations that this does not provide.
-         */
-        public static final int DECORATION_MINIMAL = 1;
-
-        /**
-         * Notifications will be partially decorated with AT LEAST an icon and expander as follows:
-         * <li>A large icon is shown if provided.
-         * <li>A progress bar is shown if provided and enough space remains below the content.
-         * <li>Actions are shown in the expanded and heads up states.
-         * <li>The collapsed state gives the app's custom content 48dp of vertical space.
-         * <li>The collapsed state does NOT include the top line of views,
-         * like the alerted icon or work profile badge.
-         */
-        public static final int DECORATION_PARTIAL = 2;
-
-        /**
-         * Notifications will be fully decorated as follows:
-         * <li>A large icon is shown if provided.
-         * <li>A progress bar is shown if provided and enough space remains below the content.
-         * <li>Actions are shown in the expanded and heads up states.
-         * <li>The collapsed state gives the app's custom content 40dp of vertical space.
-         * <li>The collapsed state DOES include the top line of views,
-         * like the alerted icon or work profile badge.
-         * <li>The collapsed state's top line views will never show the title text.
-         */
-        public static final int DECORATION_FULL_COMPATIBLE = 3;
-
-        /**
-         * Notifications will be fully decorated as follows:
-         * <li>A large icon is shown if provided.
-         * <li>A progress bar is shown if provided and enough space remains below the content.
-         * <li>Actions are shown in the expanded and heads up states.
-         * <li>The collapsed state gives the app's custom content 20dp of vertical space.
-         * <li>The collapsed state DOES include the top line of views
-         * like the alerted icon or work profile badge.
-         * <li>The collapsed state's top line views will contain the title text if provided.
-         */
-        public static final int DECORATION_FULL_CONSTRAINED = 4;
-
-        /**
-         * Used by unit tests to force that this class returns its default values, which is required
-         * in cases where the ContentResolver instance is a mock.
-         * @hide
-         */
-        public static boolean sForceDefaults;
-
-        /**
-         * @return if the S notification rules should be backported to apps not yet targeting S
-         * @hide
-         */
-        public static boolean shouldBackportSNotifRules(@NonNull ContentResolver contentResolver) {
-            return false;
-        }
-
-        /**
-         * @return the decoration type to be applied to notifications with fully custom view.
-         * @hide
-         */
-        public static int getFullyCustomViewNotifDecoration(
-                @NonNull ContentResolver contentResolver) {
-            return DECORATION_MINIMAL;
-        }
-
-        /**
-         * @return the decoration type to be applied to notifications with DecoratedCustomViewStyle.
-         * @hide
-         */
-        public static int getDecoratedCustomViewNotifDecoration(
-                @NonNull ContentResolver contentResolver) {
-            return DECORATION_PARTIAL;
-        }
-    }
 }
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 11adc5a..f4b9542 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1010,7 +1010,7 @@
      * @deprecated Renamed to {@link #getCreatorPackage()}.
      */
     @Deprecated
-    @NonNull
+    @Nullable
     public String getTargetPackage() {
         return getCreatorPackage();
     }
@@ -1032,7 +1032,7 @@
      *
      * @return The package name of the PendingIntent.
      */
-    @NonNull
+    @Nullable
     public String getCreatorPackage() {
         return getCachedInfo().getCreatorPackage();
     }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index ac8d3a2..74134e1 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -39,13 +39,13 @@
 import android.os.Process;
 import android.os.Trace;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.window.WindowContext;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -61,7 +61,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.WeakHashMap;
@@ -168,7 +167,7 @@
 
     /**
      * Class containing the base configuration override and set of resources associated with an
-     * Activity or {@link WindowContext}.
+     * {@link Activity} or a {@link WindowContext}.
      */
     private static class ActivityResources {
         /**
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3ef6757..6a71c92 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1962,14 +1962,20 @@
     }
 
     /**
-     * Set the current zoom out level of the wallpaper
+     * Set the current zoom out level of the wallpaper.
+     *
+     * @param windowToken window requesting wallpaper zoom. Zoom level will only be applier while
+     *                    such window is visible.
      * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in
      *
      * @hide
      */
-    public void setWallpaperZoomOut(IBinder windowToken, float zoom) {
+    public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
         if (zoom < 0 || zoom > 1f) {
-            throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom);
+            throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
+        }
+        if (windowToken == null) {
+            throw new IllegalArgumentException("windowToken must not be null");
         }
         try {
             WindowManagerGlobal.getWindowSession().setWallpaperZoomOut(windowToken, zoom);
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index dd2ba7d..e645831 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -56,6 +56,7 @@
     private CharSequence mNotificationContent;
     private String mNotificationCategory;
     private Uri mNotificationDataUri;
+    private int mMessagesCount;
     private Intent mIntent;
     private long mNotificationTimestamp;
     private List<ConversationStatus> mStatuses;
@@ -74,6 +75,7 @@
         mNotificationContent = b.mNotificationContent;
         mNotificationCategory = b.mNotificationCategory;
         mNotificationDataUri = b.mNotificationDataUri;
+        mMessagesCount = b.mMessagesCount;
         mIntent = b.mIntent;
         mNotificationTimestamp = b.mNotificationTimestamp;
         mStatuses = b.mStatuses;
@@ -140,6 +142,10 @@
         return mNotificationDataUri;
     }
 
+    public int getMessagesCount() {
+        return mMessagesCount;
+    }
+
     /**
      * Provides an intent to launch. If present, we should manually launch the intent on tile
      * click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID.
@@ -175,6 +181,7 @@
         builder.setNotificationContent(mNotificationContent);
         builder.setNotificationCategory(mNotificationCategory);
         builder.setNotificationDataUri(mNotificationDataUri);
+        builder.setMessagesCount(mMessagesCount);
         builder.setIntent(mIntent);
         builder.setNotificationTimestamp(mNotificationTimestamp);
         builder.setStatuses(mStatuses);
@@ -196,6 +203,7 @@
         private CharSequence mNotificationContent;
         private String mNotificationCategory;
         private Uri mNotificationDataUri;
+        private int mMessagesCount;
         private Intent mIntent;
         private long mNotificationTimestamp;
         private List<ConversationStatus> mStatuses;
@@ -320,6 +328,12 @@
             return this;
         }
 
+        /** Sets the number of messages associated with the Tile. */
+        public Builder setMessagesCount(int messagesCount) {
+            mMessagesCount = messagesCount;
+            return this;
+        }
+
         /** Sets an intent to launch on click. */
         public Builder setIntent(Intent intent) {
             mIntent = intent;
@@ -359,6 +373,7 @@
         mNotificationContent = in.readCharSequence();
         mNotificationCategory = in.readString();
         mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
+        mMessagesCount = in.readInt();
         mIntent = in.readParcelable(Intent.class.getClassLoader());
         mNotificationTimestamp = in.readLong();
         mStatuses = new ArrayList<>();
@@ -385,6 +400,7 @@
         dest.writeCharSequence(mNotificationContent);
         dest.writeString(mNotificationCategory);
         dest.writeParcelable(mNotificationDataUri, flags);
+        dest.writeInt(mMessagesCount);
         dest.writeParcelable(mIntent, flags);
         dest.writeLong(mNotificationTimestamp);
         dest.writeParcelableList(mStatuses, flags);
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index c8fa5c8..c71badb0 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -264,7 +264,7 @@
      * See {@link ExternalTimeSuggestion} for more details.
      * {@hide}
      */
-    @RequiresPermission(android.Manifest.permission.SET_TIME)
+    @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME)
     public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) {
         if (DEBUG) {
             Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index de77848..a36da88 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -33,7 +33,7 @@
  */
 @SystemApi
 @SystemService(Context.APP_HIBERNATION_SERVICE)
-public final class AppHibernationManager {
+public class AppHibernationManager {
     private static final String TAG = "AppHibernationManager";
     private final Context mContext;
     private final IAppHibernationService mIAppHibernationService;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 8908649..a96c14f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -26,6 +26,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PropertyInvalidatedCache;
+import android.companion.AssociationRequest;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -1231,8 +1232,7 @@
     }
 
     /**
-     * Get the Bluetooth alias of the remote device.
-     * <p>Alias is the locally modified name of a remote device.
+     * Get the locally modifiable name (alias) of the remote Bluetooth device.
      *
      * @return the Bluetooth alias, the friendly device name if no alias, or
      * null if there was a problem
@@ -1258,25 +1258,35 @@
     }
 
     /**
-     * Set the Bluetooth alias of the remote device.
-     * <p>Alias is the locally modified name of a remote device.
-     * <p>This methoid overwrites the alias. The changed
-     * alias is saved in the local storage so that the change
-     * is preserved over power cycle.
+     * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method
+     * overwrites the previously stored alias. The new alias is saved in local
+     * storage so that the change is preserved over power cycles.
      *
-     * @return true on success, false on error
-     * @hide
+     * <p>This method requires the calling app to be associated with Companion Device Manager (see
+     * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest,
+     * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the {@link
+     * android.Manifest.permission#BLUETOOTH} permission. Alternatively, if the caller has the
+     * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can bypass the
+     * Companion Device Manager association requirement.
+     *
+     * @param alias is the new locally modifiable name for the remote Bluetooth device which must be
+     *              non-null and not the empty string.
+     * @return {@code true} if the alias is successfully set, {@code false} on error
+     * @throws IllegalArgumentException if the alias is {@code null} or the empty string
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public boolean setAlias(@NonNull String alias) {
+        if (alias == null || alias.isEmpty()) {
+            throw new IllegalArgumentException("Cannot set the alias to null or the empty string");
+        }
         final IBluetooth service = sService;
         if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
             return false;
         }
         try {
-            return service.setRemoteAlias(this, alias);
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            return service.setRemoteAlias(this, alias, adapter.getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 7459f87..27c579b 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -587,7 +587,7 @@
          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
          */
         public Builder setDeviceAddress(String deviceAddress) {
-            return setDeviceAddress(mDeviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+            return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
         }
 
         /**
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b441b36..86bd8a2 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.app.Application;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentSender;
@@ -331,6 +332,33 @@
     }
 
     /**
+     * Checks whether the bluetooth device represented by the mac address was recently associated
+     * with the companion app. This allows these devices to skip the Bluetooth pairing dialog if
+     * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}.
+     *
+     * @param packageName the package name of the calling app
+     * @param deviceMacAddress the bluetooth device's mac address
+     * @param userId the calling user's identifier
+     * @return true if it was recently associated and we can bypass the dialog, false otherwise
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+    public boolean canPairWithoutPrompt(@NonNull String packageName,
+            @NonNull String deviceMacAddress, int userId) {
+        if (!checkFeaturePresent()) {
+            return false;
+        }
+        Objects.requireNonNull(packageName, "package name cannot be null");
+        Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null");
+        try {
+            return mService.canPairWithoutPrompt(packageName, deviceMacAddress, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Register to receive callbacks whenever the associated device comes in and out of range.
      *
      * The provided device must be {@link #associate associated} with the calling app before
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 92ff640..45a8ffd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -81,6 +81,7 @@
 import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
 import android.view.textclassifier.TextClassificationManager;
+import android.window.WindowContext;
 
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.compat.IPlatformCompatNative;
@@ -6793,4 +6794,15 @@
     public boolean isUiContext() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
+
+    /**
+     * Called when a {@link Context} is going to be released.
+     * This method can be overridden to perform the final cleanups, such as release
+     * {@link BroadcastReceiver} registrations.
+     *
+     * @see WindowContext#destroy()
+     *
+     * @hide
+     */
+    public void destroy() { }
 }
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index d0d406a..01b554a 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -8,3 +8,5 @@
 per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
 per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS
 per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS
+per-file ComponentCallbacksController = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksController = charlesccchen@google.com
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1a5dad5..0da453d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1411,6 +1411,13 @@
     private Boolean nativeHeapZeroInit;
 
     /**
+     * If {@code true} this app requests optimized external storage access.
+     * The request may not be honored due to policy or other reasons.
+     */
+    @Nullable
+    private Boolean requestOptimizedExternalStorageAccess;
+
+    /**
      * Represents the default policy. The actual policy used will depend on other properties of
      * the application, e.g. the target SDK version.
      * @hide
@@ -1566,6 +1573,10 @@
             if (nativeHeapZeroInit != null) {
                 pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
             }
+            if (requestOptimizedExternalStorageAccess != null) {
+                pw.println(prefix + "requestOptimizedExternalStorageAccess="
+                        + requestOptimizedExternalStorageAccess);
+            }
         }
         super.dumpBack(pw, prefix);
     }
@@ -1792,6 +1803,7 @@
         gwpAsanMode = orig.gwpAsanMode;
         memtagMode = orig.memtagMode;
         nativeHeapZeroInit = orig.nativeHeapZeroInit;
+        requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess;
     }
 
     public String toString() {
@@ -1880,6 +1892,7 @@
         dest.writeInt(gwpAsanMode);
         dest.writeInt(memtagMode);
         sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
+        sForBoolean.parcel(requestOptimizedExternalStorageAccess, dest, parcelableFlags);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1965,6 +1978,7 @@
         gwpAsanMode = source.readInt();
         memtagMode = source.readInt();
         nativeHeapZeroInit = sForBoolean.unparcel(source);
+        requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source);
     }
 
     /**
@@ -2079,6 +2093,24 @@
     }
 
     /**
+     * @return
+     * <ul>
+     * <li>{@code true} if this app requested optimized external storage access
+     * <li>{@code false} if this app requests to disable optimized external storage access.
+     * <li>{@code null} if the app didn't specify
+     * {@link android.R.styleable#AndroidManifestApplication_requestOptimizedExternalStorageAccess}
+     * in its manifest file.
+     * </ul>
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public Boolean hasRequestOptimizedExternalStorageAccess() {
+        return requestOptimizedExternalStorageAccess;
+    }
+
+    /**
      * If {@code true} this app allows heap pointer tagging.
      *
      * @hide
@@ -2351,6 +2383,10 @@
     /** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
     /** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
     /** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; }
+    /** {@hide} */
+    public void setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+        requestOptimizedExternalStorageAccess = value;
+    }
 
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index ba6416d..4dc9ce8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -257,6 +257,9 @@
 
     ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
 
+    ParsingPackage setRequestOptimizedExternalStorageAccess(
+            @Nullable Boolean requestOptimizedExternalStorageAccess);
+
     ParsingPackage setCrossProfile(boolean crossProfile);
 
     ParsingPackage setFullBackupContent(int fullBackupContent);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index b3c26ab..065ed2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -389,6 +389,10 @@
     @DataClass.ParcelWith(ForBoolean.class)
     private Boolean nativeHeapZeroInit;
 
+    @Nullable
+    @DataClass.ParcelWith(ForBoolean.class)
+    private Boolean requestOptimizedExternalStorageAccess;
+
     // TODO(chiuwinson): Non-null
     @Nullable
     private ArraySet<String> mimeGroups;
@@ -1068,6 +1072,7 @@
         appInfo.setGwpAsanMode(gwpAsanMode);
         appInfo.setMemtagMode(memtagMode);
         appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
+        appInfo.setRequestOptimizedExternalStorageAccess(requestOptimizedExternalStorageAccess);
         appInfo.setBaseCodePath(mBaseApkPath);
         appInfo.setBaseResourcePath(mBaseApkPath);
         appInfo.setCodePath(mPath);
@@ -1203,6 +1208,7 @@
         dest.writeMap(this.mProperties);
         dest.writeInt(this.memtagMode);
         sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
+        sForBoolean.parcel(this.requestOptimizedExternalStorageAccess, dest, flags);
     }
 
     public ParsingPackageImpl(Parcel in) {
@@ -1326,6 +1332,7 @@
         this.mProperties = in.createTypedArrayMap(Property.CREATOR);
         this.memtagMode = in.readInt();
         this.nativeHeapZeroInit = sForBoolean.unparcel(in);
+        this.requestOptimizedExternalStorageAccess = sForBoolean.unparcel(in);
         assignDerivedFields();
     }
 
@@ -2105,6 +2112,12 @@
         return nativeHeapZeroInit;
     }
 
+    @Nullable
+    @Override
+    public Boolean hasRequestOptimizedExternalStorageAccess() {
+        return requestOptimizedExternalStorageAccess;
+    }
+
     @Override
     public boolean isPartiallyDirectBootAware() {
         return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
@@ -2555,6 +2568,11 @@
     }
 
     @Override
+    public ParsingPackageImpl setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+        requestOptimizedExternalStorageAccess = value;
+        return this;
+    }
+    @Override
     public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
         return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 9f52183..47dfa9d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -902,6 +902,9 @@
     @Nullable
     Boolean isNativeHeapZeroInit();
 
+    @Nullable
+    Boolean hasRequestOptimizedExternalStorageAccess();
+
     // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
     ApplicationInfo toAppInfoWithoutState();
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 2be0157..0e1574c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2012,6 +2012,12 @@
                 pkg.setNativeHeapZeroInit(sa.getBoolean(
                         R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false));
             }
+            if (sa.hasValue(
+                    R.styleable.AndroidManifestApplication_requestOptimizedExternalStorageAccess)) {
+                pkg.setRequestOptimizedExternalStorageAccess(sa.getBoolean(R.styleable
+                                .AndroidManifestApplication_requestOptimizedExternalStorageAccess,
+                        false));
+            }
         } finally {
             sa.recycle();
         }
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index d187f60..d2d1441 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.verify.domain;
 
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,6 +30,8 @@
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 
+import com.android.internal.util.CollectionUtils;
+
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
@@ -80,22 +83,6 @@
     public static final int ERROR_DOMAIN_SET_ID_INVALID = 1;
 
     /**
-     * The provided domain set ID was null. This is a developer error.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int ERROR_DOMAIN_SET_ID_NULL = 2;
-
-    /**
-     * The provided set of domains was null or empty. This is a developer error.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3;
-
-    /**
      * The provided set of domains contains a domain not declared by the target package. This
      * usually means the work being processed by the verification agent is outdated and a new
      * request should be scheduled, which should already be in progress as part of the
@@ -104,7 +91,7 @@
      * @hide
      */
     @SystemApi
-    public static final int ERROR_UNKNOWN_DOMAIN = 4;
+    public static final int ERROR_UNKNOWN_DOMAIN = 2;
 
     /**
      * The system was unable to select the domain for approval. This indicates another application
@@ -114,17 +101,7 @@
      * @hide
      */
     @SystemApi
-    public static final int ERROR_UNABLE_TO_APPROVE = 5;
-
-    /**
-     * The provided state code is incorrect. The domain verification agent is only allowed to
-     * assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than
-     * {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int ERROR_INVALID_STATE_CODE = 6;
+    public static final int ERROR_UNABLE_TO_APPROVE = 3;
 
     /**
      * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API.
@@ -138,11 +115,8 @@
      */
     @IntDef(prefix = {"ERROR_"}, value = {
             ERROR_DOMAIN_SET_ID_INVALID,
-            ERROR_DOMAIN_SET_ID_NULL,
-            ERROR_DOMAIN_SET_NULL_OR_EMPTY,
             ERROR_UNKNOWN_DOMAIN,
             ERROR_UNABLE_TO_APPROVE,
-            ERROR_INVALID_STATE_CODE
     })
     public @interface Error {
     }
@@ -232,19 +206,21 @@
      * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
      * @param domains     List of host names to change the state of.
      * @param state       See {@link DomainVerificationInfo#getHostToStateMap()}.
-     * @throws NameNotFoundException    If the ID is known to be good, but the package is
-     *                                  unavailable. This may be because the package is installed on
-     *                                  a volume that is no longer mounted. This error is
-     *                                  unrecoverable until the package is available again, and
-     *                                  should not be re-tried except on a time scheduled basis.
      * @return error code or {@link #STATUS_OK} if successful
-     *
+     * @throws NameNotFoundException If the ID is known to be good, but the package is
+     *                               unavailable. This may be because the package is installed on
+     *                               a volume that is no longer mounted. This error is
+     *                               unrecoverable until the package is available again, and
+     *                               should not be re-tried except on a time scheduled basis.
      * @hide
      */
+    @CheckResult
     @SystemApi
     @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
     public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
             int state) throws NameNotFoundException {
+        validateInput(domainSetId, domains);
+
         try {
             return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
                     new DomainSet(domains), state);
@@ -312,19 +288,21 @@
      * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
      * @param domains     The domains to toggle the state of.
      * @param enabled     Whether or not the app should automatically open the domains specified.
-     * @throws NameNotFoundException    If the ID is known to be good, but the package is
-     *                                  unavailable. This may be because the package is installed on
-     *                                  a volume that is no longer mounted. This error is
-     *                                  unrecoverable until the package is available again, and
-     *                                  should not be re-tried except on a time scheduled basis.
      * @return error code or {@link #STATUS_OK} if successful
-     *
+     * @throws NameNotFoundException If the ID is known to be good, but the package is
+     *                               unavailable. This may be because the package is installed on
+     *                               a volume that is no longer mounted. This error is
+     *                               unrecoverable until the package is available again, and
+     *                               should not be re-tried except on a time scheduled basis.
      * @hide
      */
+    @CheckResult
     @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
     public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
             @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
+        validateInput(domainSetId, domains);
+
         try {
             return mDomainVerificationManager.setDomainVerificationUserSelection(
                     domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId());
@@ -405,4 +383,12 @@
             return exception;
         }
     }
+
+    private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) {
+        if (domainSetId == null) {
+            throw new IllegalArgumentException("domainSetId cannot be null");
+        } else if (CollectionUtils.isEmpty(domains)) {
+            throw new IllegalArgumentException("Provided domain set cannot be empty");
+        }
+    }
 }
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
index 4078b24..74c3ba4 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -23,6 +23,6 @@
  */
 oneway interface INetworkStatsProvider {
     void onRequestStatsUpdate(int token);
-    void onSetLimit(String iface, long quotaBytes);
     void onSetAlert(long quotaBytes);
+    void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes);
 }
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
index bd336dd..7eaa01e 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -26,6 +26,6 @@
 oneway interface INetworkStatsProviderCallback {
     void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
     void notifyAlertReached();
-    void notifyLimitReached();
+    void notifyWarningOrLimitReached();
     void unregister();
 }
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
index 7639d22..65b336a 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -29,7 +29,8 @@
 @SystemApi
 public abstract class NetworkStatsProvider {
     /**
-     * A value used by {@link #onSetLimit} and {@link #onSetAlert} indicates there is no limit.
+     * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit}
+     * indicates there is no limit.
      */
     public static final int QUOTA_UNLIMITED = -1;
 
@@ -42,13 +43,13 @@
         }
 
         @Override
-        public void onSetLimit(String iface, long quotaBytes) {
-            NetworkStatsProvider.this.onSetLimit(iface, quotaBytes);
+        public void onSetAlert(long quotaBytes) {
+            NetworkStatsProvider.this.onSetAlert(quotaBytes);
         }
 
         @Override
-        public void onSetAlert(long quotaBytes) {
-            NetworkStatsProvider.this.onSetAlert(quotaBytes);
+        public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {
+            NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes);
         }
     };
 
@@ -145,11 +146,28 @@
     }
 
     /**
-     * Notify system that the quota set by {@code onSetLimit} has been reached.
+     * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
+     *
+     * @hide
+     */
+    // TODO: Expose as system API.
+    public void notifyWarningReached() {
+        try {
+            // Reuse the code path to notify warning reached with limit reached
+            // since framework handles them in the same way.
+            getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notify system that the quota set by {@link #onSetLimit} or limit set by
+     * {@link #onSetWarningAndLimit} has been reached.
      */
     public void notifyLimitReached() {
         try {
-            getProviderCallbackBinderOrThrow().notifyLimitReached();
+            getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
@@ -180,9 +198,35 @@
      * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
      *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
      */
+    // TODO: deprecate this once onSetWarningAndLimit is ready.
     public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
 
     /**
+     * Called by {@code NetworkStatsService} when setting the interface quotas for the specified
+     * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system
+     * will not call {@link #onSetLimit}. When this method is called, the implementation
+     * should behave as follows:
+     *   1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on
+     *      {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}.
+     *   2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on
+     *   {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}.
+     *
+     * @param iface the interface requiring the operation.
+     * @param warningBytes the warning defined as the number of bytes, starting from zero and
+     *                     counting from now. A value of {@link #QUOTA_UNLIMITED} indicates
+     *                     there is no warning.
+     * @param limitBytes the limit defined as the number of bytes, starting from zero and counting
+     *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+     *
+     * @hide
+     */
+    // TODO: Expose as system API.
+    public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
+        // Backward compatibility for those who didn't override this function.
+        onSetLimit(iface, limitBytes);
+    }
+
+    /**
      * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
      * MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes
      * have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 6a3cb42..5b79f73 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -29,7 +29,7 @@
  */
 interface IVcnManagementService {
     void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
-    void clearVcnConfig(in ParcelUuid subscriptionGroup);
+    void clearVcnConfig(in ParcelUuid subscriptionGroup, in String opPkgName);
 
     void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index b73fdbf..abd41da 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -154,7 +154,7 @@
         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
 
         try {
-            mService.clearVcnConfig(subscriptionGroup);
+            mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName());
         } catch (ServiceSpecificException e) {
             throw new IOException(e);
         } catch (RemoteException e) {
@@ -439,7 +439,7 @@
          * @param statusCode the code for the status change encountered by this {@link
          *     VcnStatusCallback}'s subscription group.
          */
-        public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode);
+        public abstract void onStatusChanged(@VcnStatusCode int statusCode);
 
         /**
          * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
@@ -476,7 +476,7 @@
      * and there is a VCN active for its specified subscription group (this may happen after the
      * callback is registered).
      *
-     * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the
+     * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the
      * current status for the specified subscription group's VCN. If the registrant is not
      * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
      * returned.
@@ -580,7 +580,7 @@
         @Override
         public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode)));
+                    () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
         }
 
         // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f12eb88..a0721c3 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -34,7 +34,7 @@
 public final class BatteryUsageStats implements Parcelable {
     private final double mConsumedPower;
     private final int mDischargePercentage;
-    private final long mStatsStartRealtimeMs;
+    private final long mStatsStartTimestampMs;
     private final double mDischargedPowerLowerBound;
     private final double mDischargedPowerUpperBound;
     private final long mBatteryTimeRemainingMs;
@@ -46,7 +46,7 @@
     private final List<BatteryStats.HistoryTag> mHistoryTagPool;
 
     private BatteryUsageStats(@NonNull Builder builder) {
-        mStatsStartRealtimeMs = builder.mStatsStartRealtimeMs;
+        mStatsStartTimestampMs = builder.mStatsStartTimestampMs;
         mDischargePercentage = builder.mDischargePercentage;
         mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
         mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
@@ -91,10 +91,11 @@
     }
 
     /**
-     * Timestamp of the latest battery stats reset, in milliseconds.
+     * Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in
+     * milliseconds.
      */
-    public long getStatsStartRealtime() {
-        return mStatsStartRealtimeMs;
+    public long getStatsStartTimestamp() {
+        return mStatsStartTimestampMs;
     }
 
     /**
@@ -174,7 +175,7 @@
     }
 
     private BatteryUsageStats(@NonNull Parcel source) {
-        mStatsStartRealtimeMs = source.readLong();
+        mStatsStartTimestampMs = source.readLong();
         mConsumedPower = source.readDouble();
         mDischargePercentage = source.readInt();
         mDischargedPowerLowerBound = source.readDouble();
@@ -214,7 +215,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeLong(mStatsStartRealtimeMs);
+        dest.writeLong(mStatsStartTimestampMs);
         dest.writeDouble(mConsumedPower);
         dest.writeInt(mDischargePercentage);
         dest.writeDouble(mDischargedPowerLowerBound);
@@ -260,7 +261,7 @@
     public static final class Builder {
         private final int mCustomPowerComponentCount;
         private final int mCustomTimeComponentCount;
-        private long mStatsStartRealtimeMs;
+        private long mStatsStartTimestampMs;
         private int mDischargePercentage;
         private double mDischargedPowerLowerBoundMah;
         private double mDischargedPowerUpperBoundMah;
@@ -291,8 +292,8 @@
         /**
          * Sets the timestamp of the latest battery stats reset, in milliseconds.
          */
-        public Builder setStatsStartRealtime(long statsStartRealtimeMs) {
-            mStatsStartRealtimeMs = statsStartRealtimeMs;
+        public Builder setStatsStartTimestamp(long statsStartTimestampMs) {
+            mStatsStartTimestampMs = statsStartTimestampMs;
             return this;
         }
 
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 95b5e85..c78bf8c 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -21,6 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
@@ -30,6 +31,7 @@
 import android.net.Uri;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.MathUtils;
@@ -250,7 +252,7 @@
         for (int i = 0; i < timings.length; i++) {
             float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
                     ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
-            segments.add(new StepSegment(parsedAmplitude, (int) timings[i]));
+            segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i]));
         }
         VibrationEffect effect = new Composed(segments, repeat);
         effect.validate();
@@ -389,8 +391,26 @@
      * @see VibrationEffect.Composition
      */
     @NonNull
-    public static VibrationEffect.Composition startComposition() {
-        return new VibrationEffect.Composition();
+    public static Composition startComposition() {
+        return new Composition();
+    }
+
+    /**
+     * Start building a waveform vibration.
+     *
+     * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+     * control over vibration frequency and ramping up or down the vibration amplitude, frequency or
+     * both.
+     *
+     * <p>For simpler waveform patterns see {@link #createWaveform} methods.
+     *
+     * @hide
+     * @see VibrationEffect.WaveformBuilder
+     */
+    @TestApi
+    @NonNull
+    public static WaveformBuilder startWaveform() {
+        return new WaveformBuilder();
     }
 
     @Override
@@ -771,6 +791,42 @@
         Composition() {}
 
         /**
+         * Add a haptic effect to the end of the current composition.
+         *
+         * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied.
+         *
+         * @param effect The effect to add to this composition as a primitive
+         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+         * @hide
+         */
+        @TestApi
+        @NonNull
+        public Composition addEffect(@NonNull VibrationEffect effect) {
+            return addEffect(effect, /* delay= */ 0);
+        }
+
+        /**
+         * Add a haptic effect to the end of the current composition.
+         *
+         * @param effect The effect to add to this composition as a primitive
+         * @param delay  The amount of time in milliseconds to wait before playing this primitive
+         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+         * @hide
+         */
+        @TestApi
+        @NonNull
+        public Composition addEffect(@NonNull VibrationEffect effect,
+                @IntRange(from = 0) int delay) {
+            Preconditions.checkArgumentNonnegative(delay);
+            if (delay > 0) {
+                // Created a segment sustaining the zero amplitude to represent the delay.
+                addSegment(new StepSegment(/* amplitude= */ 0, /* frequency= */ 0,
+                        /* duration= */ delay));
+            }
+            return addSegments(effect);
+        }
+
+        /**
          * Add a haptic primitive to the end of the current composition.
          *
          * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
@@ -829,6 +885,21 @@
             return this;
         }
 
+        private Composition addSegments(VibrationEffect effect) {
+            if (mRepeatIndex >= 0) {
+                throw new IllegalStateException(
+                        "Composition already have a repeating effect so any new primitive would be"
+                                + " unreachable.");
+            }
+            Composed composed = (Composed) effect;
+            if (composed.getRepeatIndex() >= 0) {
+                // Start repeating from the index relative to the composed waveform.
+                mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
+            }
+            mSegments.addAll(composed.getSegments());
+            return this;
+        }
+
         /**
          * Compose all of the added primitives together into a single {@link VibrationEffect}.
          *
@@ -881,6 +952,164 @@
         }
     }
 
+    /**
+     * A builder for waveform haptic effects.
+     *
+     * <p>Waveform vibrations constitute of one or more timed segments where the vibration
+     * amplitude, frequency or both can linearly ramp to new values.
+     *
+     * <p>Waveform segments may have zero duration, which represent a jump to new vibration
+     * amplitude and/or frequency values.
+     *
+     * <p>Waveform segments may have the same start and end vibration amplitude and frequency,
+     * which represent a step where the amplitude and frequency are maintained for that duration.
+     *
+     * @hide
+     * @see VibrationEffect#startWaveform()
+     */
+    @TestApi
+    public static final class WaveformBuilder {
+        private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+
+        WaveformBuilder() {}
+
+        /**
+         * Vibrate with given amplitude for the given duration, in millis, keeping the previous
+         * frequency the same.
+         *
+         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         *
+         * @param amplitude The amplitude for this step
+         * @param duration  The duration of this step in milliseconds
+         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
+                @IntRange(from = 0) int duration) {
+            return addStep(amplitude, getPreviousFrequency(), duration);
+        }
+
+        /**
+         * Vibrate with given amplitude for the given duration, in millis, keeping the previous
+         * vibration frequency the same.
+         *
+         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         *
+         * @param amplitude The amplitude for this step
+         * @param frequency The frequency for this step
+         * @param duration  The duration of this step in milliseconds
+         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
+                @FloatRange(from = -1f, to = 1f) float frequency,
+                @IntRange(from = 0) int duration) {
+            mSegments.add(new StepSegment(amplitude, frequency, duration));
+            return this;
+        }
+
+        /**
+         * Ramp vibration linearly for the given duration, in millis, from previous amplitude value
+         * to the given one, keeping previous frequency.
+         *
+         * <p>If the duration is zero the vibrator will jump to new amplitude.
+         *
+         * @param amplitude The final amplitude this ramp should reach
+         * @param duration  The duration of this ramp in milliseconds
+         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
+                @IntRange(from = 0) int duration) {
+            return addRamp(amplitude, getPreviousFrequency(), duration);
+        }
+
+        /**
+         * Ramp vibration linearly for the given duration, in millis, from previous amplitude and
+         * frequency values to the given ones.
+         *
+         * <p>If the duration is zero the vibrator will jump to new amplitude and frequency.
+         *
+         * @param amplitude The final amplitude this ramp should reach
+         * @param frequency The final frequency this ramp should reach
+         * @param duration  The duration of this ramp in milliseconds
+         * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
+                @FloatRange(from = -1f, to = 1f) float frequency,
+                @IntRange(from = 0) int duration) {
+            mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(),
+                    frequency, duration));
+            return this;
+        }
+
+        /**
+         * Compose all of the steps together into a single {@link VibrationEffect}.
+         *
+         * The {@link WaveformBuilder} object is still valid after this call, so you can
+         * continue adding more primitives to it and generating more {@link VibrationEffect}s by
+         * calling this method again.
+         *
+         * @return The {@link VibrationEffect} resulting from the composition of the steps.
+         */
+        @NonNull
+        public VibrationEffect build() {
+            return build(/* repeat= */ -1);
+        }
+
+        /**
+         * Compose all of the steps together into a single {@link VibrationEffect}.
+         *
+         * <p>To cause the pattern to repeat, pass the index at which to start the repetition
+         * (starting at 0), or -1 to disable repeating.
+         *
+         * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
+         * continue adding more primitives to it and generating more {@link VibrationEffect}s by
+         * calling this method again.
+         *
+         * @return The {@link VibrationEffect} resulting from the composition of the steps.
+         */
+        @NonNull
+        public VibrationEffect build(int repeat) {
+            if (mSegments.isEmpty()) {
+                throw new IllegalStateException(
+                        "WaveformBuilder must have at least one element to build.");
+            }
+            VibrationEffect effect = new Composed(mSegments, repeat);
+            effect.validate();
+            return effect;
+        }
+
+        private float getPreviousFrequency() {
+            if (!mSegments.isEmpty()) {
+                VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
+                if (segment instanceof StepSegment) {
+                    return ((StepSegment) segment).getFrequency();
+                } else if (segment instanceof RampSegment) {
+                    return ((RampSegment) segment).getEndFrequency();
+                }
+            }
+            return 0;
+        }
+
+        private float getPreviousAmplitude() {
+            if (!mSegments.isEmpty()) {
+                VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
+                if (segment instanceof StepSegment) {
+                    return ((StepSegment) segment).getAmplitude();
+                } else if (segment instanceof RampSegment) {
+                    return ((RampSegment) segment).getEndAmplitude();
+                }
+            }
+            return 0;
+        }
+    }
+
     @NonNull
     public static final Parcelable.Creator<VibrationEffect> CREATOR =
             new Parcelable.Creator<VibrationEffect>() {
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 9c46bc9..64e51e7 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,9 +16,13 @@
 
 package android.os;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.vibrator.IVibrator;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Range;
 import android.util.SparseBooleanArray;
 
 import java.util.ArrayList;
@@ -42,27 +46,27 @@
     private final SparseBooleanArray mSupportedEffects;
     @Nullable
     private final SparseBooleanArray mSupportedPrimitives;
-    private final float mResonantFrequency;
     private final float mQFactor;
+    private final FrequencyMapping mFrequencyMapping;
 
     VibratorInfo(Parcel in) {
         mId = in.readInt();
         mCapabilities = in.readLong();
         mSupportedEffects = in.readSparseBooleanArray();
         mSupportedPrimitives = in.readSparseBooleanArray();
-        mResonantFrequency = in.readFloat();
         mQFactor = in.readFloat();
+        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
     }
 
     /** @hide */
     public VibratorInfo(int id, long capabilities, int[] supportedEffects,
-            int[] supportedPrimitives, float resonantFrequency, float qFactor) {
+            int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) {
         mId = id;
         mCapabilities = capabilities;
         mSupportedEffects = toSparseBooleanArray(supportedEffects);
         mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
-        mResonantFrequency = resonantFrequency;
         mQFactor = qFactor;
+        mFrequencyMapping = frequencyMapping;
     }
 
     @Override
@@ -71,8 +75,8 @@
         dest.writeLong(mCapabilities);
         dest.writeSparseBooleanArray(mSupportedEffects);
         dest.writeSparseBooleanArray(mSupportedPrimitives);
-        dest.writeFloat(mResonantFrequency);
         dest.writeFloat(mQFactor);
+        dest.writeParcelable(mFrequencyMapping, flags);
     }
 
     @Override
@@ -92,14 +96,14 @@
         return mId == that.mId && mCapabilities == that.mCapabilities
                 && Objects.equals(mSupportedEffects, that.mSupportedEffects)
                 && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
-                && Objects.equals(mResonantFrequency, that.mResonantFrequency)
-                && Objects.equals(mQFactor, that.mQFactor);
+                && Objects.equals(mQFactor, that.mQFactor)
+                && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
-                mResonantFrequency, mQFactor);
+                mQFactor, mFrequencyMapping);
     }
 
     @Override
@@ -110,8 +114,8 @@
                 + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
                 + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
                 + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
-                + ", mResonantFrequency=" + mResonantFrequency
                 + ", mQFactor=" + mQFactor
+                + ", mFrequencyMapping=" + mFrequencyMapping
                 + '}';
     }
 
@@ -177,7 +181,7 @@
      *         this vibrator is a composite of multiple physical devices.
      */
     public float getResonantFrequency() {
-        return mResonantFrequency;
+        return mFrequencyMapping.mResonantFrequencyHz;
     }
 
     /**
@@ -190,6 +194,52 @@
         return mQFactor;
     }
 
+    /**
+     * Return a range of relative frequency values supported by the vibrator.
+     *
+     * @return A range of relative frequency values supported. The range will always contain the
+     * value 0, representing the device resonant frequency. Devices without frequency control will
+     * return the range [0,0]. Devices with frequency control will always return a range containing
+     * the safe range [-1, 1].
+     * @hide
+     */
+    public Range<Float> getFrequencyRange() {
+        return mFrequencyMapping.mRelativeFrequencyRange;
+    }
+
+    /**
+     * Return the maximum amplitude the vibrator can play at given relative frequency.
+     *
+     * @return a value in [0,1] representing the maximum amplitude the device can play at given
+     * relative frequency. Devices without frequency control will return 1 for the input zero
+     * (resonant frequency), and 0 to any other input. Devices with frequency control will return
+     * the supported value, for input in {@code #getFrequencyRange()}, and 0 for any other input.
+     * @hide
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMaxAmplitude(float relativeFrequency) {
+        if (mFrequencyMapping.isEmpty()) {
+            // The vibrator has not provided values for frequency mapping.
+            // Return the expected behavior for devices without frequency control.
+            return Float.compare(relativeFrequency, 0) == 0 ? 1 : 0;
+        }
+        return mFrequencyMapping.getMaxAmplitude(relativeFrequency);
+    }
+
+    /**
+     * Return absolute frequency value for this vibrator, in hertz, that corresponds to given
+     * relative frequency.
+     *
+     * @retur a value in hertz that corresponds to given relative frequency. Input values outside
+     * {@link #getFrequencyRange()} will return {@link Float#NaN}. Devices without frequency control
+     * will return {@link Float#NaN} for any input.
+     * @hide
+     */
+    @FloatRange(from = 0)
+    public float getAbsoluteFrequency(float relativeFrequency) {
+        return mFrequencyMapping.toHertz(relativeFrequency);
+    }
+
     private String[] getCapabilitiesNames() {
         List<String> names = new ArrayList<>();
         if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
@@ -250,6 +300,209 @@
         return array;
     }
 
+    /**
+     * Describes how frequency should be mapped to absolute values for a specific {@link Vibrator}.
+     *
+     * <p>This mapping is defined by the following parameters:
+     *
+     * <ol>
+     *     <li>{@code minFrequency}, {@code resonantFrequency} and {@code frequencyResolution}, in
+     *         hertz, provided by the vibrator.
+     *     <li>{@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where
+     *         {@code maxAmplitudes[i]} represents max supported amplitude at frequency
+     *         {@code minFrequency + frequencyResolution * i}.
+     *     <li>{@code maxFrequency = minFrequency + frequencyResolution * (maxAmplitudes.length-1)}
+     *     <li>{@code suggestedSafeRangeHz} is the suggested frequency range in hertz that should be
+     *         mapped to relative values -1 and 1, where 0 maps to {@code resonantFrequency}.
+     * </ol>
+     *
+     * <p>The mapping is defined linearly by the following points:
+     *
+     * <ol>
+     *     <li>{@code toHertz(relativeMinFrequency} = minFrequency
+     *     <li>{@code                   toHertz(-1) = resonantFrequency - safeRange / 2}
+     *     <li>{@code                    toHertz(0) = resonantFrequency}
+     *     <li>{@code                    toHertz(1) = resonantFrequency + safeRange / 2}
+     *     <li>{@code toHertz(relativeMaxFrequency) = maxFrequency}
+     * </ol>
+     *
+     * @hide
+     */
+    public static final class FrequencyMapping implements Parcelable {
+        private final float mMinFrequencyHz;
+        private final float mResonantFrequencyHz;
+        private final float mFrequencyResolutionHz;
+        private final float mSuggestedSafeRangeHz;
+        private final float[] mMaxAmplitudes;
+
+        // Relative fields calculated from input values:
+        private final Range<Float> mRelativeFrequencyRange;
+
+        FrequencyMapping(Parcel in) {
+            this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(),
+                    in.createFloatArray());
+        }
+
+        /** @hide */
+        public FrequencyMapping(float minFrequencyHz, float resonantFrequencyHz,
+                float frequencyResolutionHz, float suggestedSafeRangeHz, float[] maxAmplitudes) {
+            mMinFrequencyHz = minFrequencyHz;
+            mResonantFrequencyHz = resonantFrequencyHz;
+            mFrequencyResolutionHz = frequencyResolutionHz;
+            mSuggestedSafeRangeHz = suggestedSafeRangeHz;
+            mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length];
+            if (maxAmplitudes != null) {
+                System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
+            }
+
+            float maxFrequencyHz =
+                    minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1);
+            if (Float.isNaN(resonantFrequencyHz) || Float.isNaN(minFrequencyHz)
+                    || Float.isNaN(frequencyResolutionHz) || Float.isNaN(suggestedSafeRangeHz)
+                    || resonantFrequencyHz < minFrequencyHz
+                    || resonantFrequencyHz > maxFrequencyHz) {
+                // Some required fields are undefined or have bad values.
+                // Leave this mapping empty.
+                mRelativeFrequencyRange = Range.create(0f, 0f);
+                return;
+            }
+
+            // Calculate actual safe range, limiting the suggested one by the device supported range
+            float safeDelta = MathUtils.min(
+                    suggestedSafeRangeHz / 2,
+                    resonantFrequencyHz - minFrequencyHz,
+                    maxFrequencyHz - resonantFrequencyHz);
+            mRelativeFrequencyRange = Range.create(
+                    (minFrequencyHz - resonantFrequencyHz) / safeDelta,
+                    (maxFrequencyHz - resonantFrequencyHz) / safeDelta);
+        }
+
+        /**
+         * Returns true if this frequency mapping is empty, i.e. the only supported relative
+         * frequency is 0 (resonant frequency).
+         */
+        public boolean isEmpty() {
+            return Float.compare(mRelativeFrequencyRange.getLower(),
+                    mRelativeFrequencyRange.getUpper()) == 0;
+        }
+
+        /**
+         * Returns the frequency value in hertz that is mapped to the given relative frequency.
+         *
+         * @return The mapped frequency, in hertz, or {@link Float#NaN} is value outside the device
+         * supported range.
+         */
+        public float toHertz(float relativeFrequency) {
+            if (!mRelativeFrequencyRange.contains(relativeFrequency)) {
+                return Float.NaN;
+            }
+            float relativeMinFrequency = mRelativeFrequencyRange.getLower();
+            if (Float.compare(relativeMinFrequency, 0) == 0) {
+                // relative supported range is [0,0], so toHertz(0) should be the resonant frequency
+                return mResonantFrequencyHz;
+            }
+            float shift = (mMinFrequencyHz - mResonantFrequencyHz) / relativeMinFrequency;
+            return mResonantFrequencyHz + relativeFrequency * shift;
+        }
+
+        /**
+         * Returns the maximum amplitude the vibrator can reach while playing at given relative
+         * frequency.
+         *
+         * @return A value in [0,1] representing the max amplitude supported at given relative
+         * frequency. This will return 0 if frequency is outside supported range, or if max
+         * amplitude mapping is empty.
+         */
+        public float getMaxAmplitude(float relativeFrequency) {
+            float frequencyHz = toHertz(relativeFrequency);
+            if (Float.isNaN(frequencyHz)) {
+                // Unsupported frequency requested, vibrator cannot play at this frequency.
+                return 0;
+            }
+            float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz;
+            int floorIndex = (int) Math.floor(position);
+            int ceilIndex = (int) Math.ceil(position);
+            if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) {
+                if (mMaxAmplitudes.length > 0) {
+                    // This should never happen if the setup of relative frequencies was correct.
+                    Log.w(TAG, "Max amplitudes has " + mMaxAmplitudes.length
+                            + " entries and was expected to cover the frequency " + frequencyHz
+                            + " Hz when starting at min frequency of " + mMinFrequencyHz
+                            + " Hz with resolution of " + mFrequencyResolutionHz + " Hz.");
+                }
+                return 0;
+            }
+            if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) {
+                // Value in between two mapped frequency values, use the lowest supported one.
+                return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]);
+            }
+            return mMaxAmplitudes[floorIndex];
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeFloat(mMinFrequencyHz);
+            dest.writeFloat(mResonantFrequencyHz);
+            dest.writeFloat(mFrequencyResolutionHz);
+            dest.writeFloat(mSuggestedSafeRangeHz);
+            dest.writeFloatArray(mMaxAmplitudes);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof FrequencyMapping)) {
+                return false;
+            }
+            FrequencyMapping that = (FrequencyMapping) o;
+            return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
+                    && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
+                    && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
+                    && Float.compare(mSuggestedSafeRangeHz, that.mSuggestedSafeRangeHz) == 0
+                    && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz,
+                    mSuggestedSafeRangeHz, mMaxAmplitudes);
+        }
+
+        @Override
+        public String toString() {
+            return "FrequencyMapping{"
+                    + "mMinFrequency=" + mMinFrequencyHz
+                    + ", mResonantFrequency=" + mResonantFrequencyHz
+                    + ", mMaxFrequency="
+                    + (mMinFrequencyHz + mFrequencyResolutionHz * (mMaxAmplitudes.length - 1))
+                    + ", mFrequencyResolution=" + mFrequencyResolutionHz
+                    + ", mSuggestedSafeRange=" + mSuggestedSafeRangeHz
+                    + ", mMaxAmplitudes count=" + mMaxAmplitudes.length
+                    + '}';
+        }
+
+        @NonNull
+        public static final Creator<FrequencyMapping> CREATOR =
+                new Creator<FrequencyMapping>() {
+                    @Override
+                    public FrequencyMapping createFromParcel(Parcel in) {
+                        return new FrequencyMapping(in);
+                    }
+
+                    @Override
+                    public FrequencyMapping[] newArray(int size) {
+                        return new FrequencyMapping[size];
+                    }
+                };
+    }
+
     @NonNull
     public static final Creator<VibratorInfo> CREATOR =
             new Creator<VibratorInfo>() {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 98b4e0b..9385402 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -201,4 +201,6 @@
     void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91;
     void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
     PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
-    }
+    boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94;
+    int getExternalStorageMountMode(int uid, in String packageName) = 95;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index c967deb..8107168 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2124,6 +2124,52 @@
         }
     }
 
+
+    /** @hide */
+    @IntDef(prefix = { "MOUNT_MODE_" }, value = {
+            MOUNT_MODE_EXTERNAL_NONE,
+            MOUNT_MODE_EXTERNAL_DEFAULT,
+            MOUNT_MODE_EXTERNAL_INSTALLER,
+            MOUNT_MODE_EXTERNAL_PASS_THROUGH,
+            MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
+    })
+    /** @hide */
+    public @interface MountMode {}
+
+    /**
+     * No external storage should be mounted.
+     * @hide
+     */
+    @SystemApi
+    public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
+    /**
+     * Default external storage should be mounted.
+     * @hide
+     */
+    @SystemApi
+    public static final int MOUNT_MODE_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
+    /**
+     * Mount mode for package installers which should give them access to
+     * all obb dirs in addition to their package sandboxes
+     * @hide
+     */
+    @SystemApi
+    public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
+    /**
+     * The lower file system should be bind mounted directly on external storage
+     * @hide
+     */
+    @SystemApi
+    public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
+    /**
+     * Use the regular scoped storage filesystem, but Android/ should be writable.
+     * Used to support the applications hosting DownloadManager and the MTP server.
+     * @hide
+     */
+    @SystemApi
+    public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE =
+            IVold.REMOUNT_MODE_ANDROID_WRITABLE;
     /**
      * Flag indicating that a disk space allocation request should operate in an
      * aggressive mode. This flag should only be rarely used in situations that
@@ -2301,6 +2347,28 @@
     }
 
     /**
+     * Returns the External Storage mount mode corresponding to the given uid and packageName.
+     * These mount modes specify different views and access levels for
+     * different apps on external storage.
+     *
+     * @params uid UID of the application
+     * @params packageName name of the package
+     * @return {@code MountMode} for the given uid and packageName.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+    @SystemApi
+    @MountMode
+    public int getExternalStorageMountMode(int uid, @NonNull String packageName) {
+        try {
+            return mStorageManager.getExternalStorageMountMode(uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Allocate the requested number of bytes for your application to use in the
      * given open file. This will cause the system to delete any cached files
      * necessary to satisfy your request.
@@ -2803,6 +2871,30 @@
         }
     }
 
+    /**
+     * Check if {@code uid} with {@code tid} is blocked on IO for {@code reason}.
+     *
+     * This requires {@link ExternalStorageService} the
+     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+     *
+     * @param volumeUuid the UUID of the storage volume to check IO blocked status
+     * @param uid the UID of the app to check IO blocked status
+     * @param tid the tid of the app to check IO blocked status
+     * @param reason the reason to check IO blocked status for
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid,
+            @AppIoBlockedReason int reason) {
+        Objects.requireNonNull(volumeUuid);
+        try {
+            return mStorageManager.isAppIoBlocked(convert(volumeUuid), uid, tid, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private final Object mFuseAppLoopLock = new Object();
 
     @GuardedBy("mFuseAppLoopLock")
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index b5abe2a..36177c4 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,6 +16,8 @@
 
 package android.os.storage;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -289,9 +291,14 @@
         return mMaxFileSize;
     }
 
-    /** {@hide} */
+    /**
+     * Returns the user that owns this volume
+     *
+     * {@hide}
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public UserHandle getOwner() {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public @NonNull UserHandle getOwner() {
         return mOwner;
     }
 
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
new file mode 100644
index 0000000..aad87c5
--- /dev/null
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency
+ * for a specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class RampSegment extends VibrationEffectSegment {
+    private final float mStartAmplitude;
+    private final float mStartFrequency;
+    private final float mEndAmplitude;
+    private final float mEndFrequency;
+    private final int mDuration;
+
+    RampSegment(@NonNull Parcel in) {
+        this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt());
+    }
+
+    /** @hide */
+    public RampSegment(float startAmplitude, float endAmplitude, float startFrequency,
+            float endFrequency, int duration) {
+        mStartAmplitude = startAmplitude;
+        mEndAmplitude = endAmplitude;
+        mStartFrequency = startFrequency;
+        mEndFrequency = endFrequency;
+        mDuration = duration;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof RampSegment)) {
+            return false;
+        }
+        RampSegment other = (RampSegment) o;
+        return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0
+                && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0
+                && Float.compare(mStartFrequency, other.mStartFrequency) == 0
+                && Float.compare(mEndFrequency, other.mEndFrequency) == 0
+                && mDuration == other.mDuration;
+    }
+
+    public float getStartAmplitude() {
+        return mStartAmplitude;
+    }
+
+    public float getEndAmplitude() {
+        return mEndAmplitude;
+    }
+
+    public float getStartFrequency() {
+        return mStartFrequency;
+    }
+
+    public float getEndFrequency() {
+        return mEndFrequency;
+    }
+
+    @Override
+    public long getDuration() {
+        return mDuration;
+    }
+
+    @Override
+    public boolean hasNonZeroAmplitude() {
+        return mStartAmplitude > 0 || mEndAmplitude > 0;
+    }
+
+    @Override
+    public void validate() {
+        Preconditions.checkArgumentNonnegative(mDuration,
+                "Durations must all be >= 0, got " + mDuration);
+        Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
+        Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
+    }
+
+
+    @NonNull
+    @Override
+    public RampSegment resolve(int defaultAmplitude) {
+        // Default amplitude is not supported for ramping.
+        return this;
+    }
+
+    @NonNull
+    @Override
+    public RampSegment scale(float scaleFactor) {
+        float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor);
+        float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor);
+        if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+                && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+            return this;
+        }
+        return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency,
+                mDuration);
+    }
+
+    @NonNull
+    @Override
+    public RampSegment applyEffectStrength(int effectStrength) {
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency,
+                mDuration);
+    }
+
+    @Override
+    public String toString() {
+        return "Ramp{startAmplitude=" + mStartAmplitude
+                + ", endAmplitude=" + mEndAmplitude
+                + ", startFrequency=" + mStartFrequency
+                + ", endFrequency=" + mEndFrequency
+                + ", duration=" + mDuration
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(PARCEL_TOKEN_RAMP);
+        out.writeFloat(mStartAmplitude);
+        out.writeFloat(mEndAmplitude);
+        out.writeFloat(mStartFrequency);
+        out.writeFloat(mEndFrequency);
+        out.writeInt(mDuration);
+    }
+
+    @NonNull
+    public static final Creator<RampSegment> CREATOR =
+            new Creator<RampSegment>() {
+                @Override
+                public RampSegment createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new RampSegment(in);
+                }
+
+                @Override
+                public RampSegment[] newArray(int size) {
+                    return new RampSegment[size];
+                }
+            };
+}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 61a5d6c..11209e0 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -27,23 +27,25 @@
 import java.util.Objects;
 
 /**
- * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a
- * specified duration.
+ * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and
+ * frequency for a specified duration.
  *
  * @hide
  */
 @TestApi
 public final class StepSegment extends VibrationEffectSegment {
     private final float mAmplitude;
+    private final float mFrequency;
     private final int mDuration;
 
     StepSegment(@NonNull Parcel in) {
-        this(in.readFloat(), in.readInt());
+        this(in.readFloat(), in.readFloat(), in.readInt());
     }
 
     /** @hide */
-    public StepSegment(float amplitude, int duration) {
+    public StepSegment(float amplitude, float frequency, int duration) {
         mAmplitude = amplitude;
+        mFrequency = frequency;
         mDuration = duration;
     }
 
@@ -54,6 +56,7 @@
         }
         StepSegment other = (StepSegment) o;
         return Float.compare(mAmplitude, other.mAmplitude) == 0
+                && Float.compare(mFrequency, other.mFrequency) == 0
                 && mDuration == other.mDuration;
     }
 
@@ -61,6 +64,10 @@
         return mAmplitude;
     }
 
+    public float getFrequency() {
+        return mFrequency;
+    }
+
     @Override
     public long getDuration() {
         return mDuration;
@@ -92,7 +99,8 @@
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
             return this;
         }
-        return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration);
+        return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency,
+                mDuration);
     }
 
     @NonNull
@@ -101,7 +109,8 @@
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
             return this;
         }
-        return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration);
+        return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency,
+                mDuration);
     }
 
     @NonNull
@@ -112,12 +121,13 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mAmplitude, mDuration);
+        return Objects.hash(mAmplitude, mFrequency, mDuration);
     }
 
     @Override
     public String toString() {
         return "Step{amplitude=" + mAmplitude
+                + ", frequency=" + mFrequency
                 + ", duration=" + mDuration
                 + "}";
     }
@@ -131,6 +141,7 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeInt(PARCEL_TOKEN_STEP);
         out.writeFloat(mAmplitude);
+        out.writeFloat(mFrequency);
         out.writeInt(mDuration);
     }
 
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 3dc9e12..5b42845 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -31,7 +31,8 @@
  * <ol>
  *     <li>A predefined vibration effect;
  *     <li>A composable effect primitive;
- *     <li>Fixed amplitude value to be held for a specified duration;
+ *     <li>Fixed amplitude and frequency values to be held for a specified duration;
+ *     <li>Pairs of amplitude and frequency values to be ramped to for a specified duration;
  * </ol>
  *
  * @hide
@@ -42,6 +43,7 @@
     static final int PARCEL_TOKEN_PREBAKED = 1;
     static final int PARCEL_TOKEN_PRIMITIVE = 2;
     static final int PARCEL_TOKEN_STEP = 3;
+    static final int PARCEL_TOKEN_RAMP = 4;
 
     /** Prevent subclassing from outside of this package */
     VibrationEffectSegment() {
@@ -96,6 +98,8 @@
                     switch (in.readInt()) {
                         case PARCEL_TOKEN_STEP:
                             return new StepSegment(in);
+                        case PARCEL_TOKEN_RAMP:
+                            return new RampSegment(in);
                         case PARCEL_TOKEN_PREBAKED:
                             return new PrebakedSegment(in);
                         case PARCEL_TOKEN_PRIMITIVE:
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 80a3e16..921911b 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -283,9 +283,7 @@
                         continue;
                     }
 
-                    if (packageName.equals(SYSTEM_PKG)
-                            || (!shouldShowPermissionsHub()
-                            && !isUserSensitive(packageName, user, op))) {
+                    if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) {
                         continue;
                     }
 
@@ -372,8 +370,10 @@
                 proxyLabels.put(usage, new ArrayList<>());
                 proxyUids.add(usage.uid);
             }
-            if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime
-                    > mostRecentUsages.get(usage.uid).lastAccessTime) {
+            // If this usage is not by the system, and is more recent than the next-most recent
+            // for it's uid, save it.
+            if (!usage.packageName.equals(SYSTEM_PKG) && (!mostRecentUsages.containsKey(usage.uid)
+                    || usage.lastAccessTime > mostRecentUsages.get(usage.uid).lastAccessTime)) {
                 mostRecentUsages.put(usage.uid, usage);
             }
         }
@@ -416,20 +416,22 @@
                 }
 
                 proxyUids.add(currentUsage.uid);
-                try {
-                    PackageManager userPkgManager =
-                            getUserContext(currentUsage.getUser()).getPackageManager();
-                    ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
-                            currentUsage.packageName, 0);
-                    CharSequence appLabel = appInfo.loadLabel(userPkgManager);
-                    // If we don't already have the app label, and it's not the same as the main
-                    // app, add it
-                    if (!proxyLabelList.contains(appLabel)
-                            && !currentUsage.packageName.equals(start.packageName)) {
-                        proxyLabelList.add(appLabel);
+                // Don't add an app label for the main app, or the system app
+                if (!currentUsage.packageName.equals(start.packageName)
+                        && !currentUsage.packageName.equals(SYSTEM_PKG)) {
+                    try {
+                        PackageManager userPkgManager =
+                                getUserContext(currentUsage.getUser()).getPackageManager();
+                        ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
+                                currentUsage.packageName, 0);
+                        CharSequence appLabel = appInfo.loadLabel(userPkgManager);
+                        // If we don't already have the app label add it
+                        if (!proxyLabelList.contains(appLabel)) {
+                            proxyLabelList.add(appLabel);
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // Ignore
                     }
-                } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore
                 }
                 iterNum++;
             }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7e40497..31cf63c 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -108,6 +108,13 @@
     public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
 
     /**
+     * Namespace for all AppSearch related features.
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_APPSEARCH = "appsearch";
+
+    /**
      * Namespace for app standby configurations.
      *
      * @hide
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0fea484..620fa65 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -236,10 +236,21 @@
     public static final String
             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
 
-    /** {@hide} */
+    /**
+     * External Storage Provider's authority string
+     * {@hide}
+     */
+    @SystemApi
     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
             "com.android.externalstorage.documents";
 
+    /**
+     * Download Manager's authority string
+     * {@hide}
+     */
+    @SystemApi
+    public static final String DOWNLOADS_PROVIDER_AUTHORITY = Downloads.Impl.AUTHORITY;
+
     /** {@hide} */
     public static final String EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary";
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 71ffa92..719c383 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1068,8 +1068,8 @@
      * Output: Nothing.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS =
-            "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+    public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS =
+            "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
 
     /**
      * Activity Action: Show screen for controlling which apps can draw on top of other apps.
@@ -8523,6 +8523,15 @@
                 "one_handed_tutorial_show_count";
 
         /**
+         * Indicates whether transform is enabled.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        public static final String TRANSFORM_ENABLED = "transform_enabled";
+
+        /**
          * The current night mode that has been selected by the user.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
          * UiModeManager.
@@ -13112,7 +13121,7 @@
          * @see #ENABLE_RESTRICTED_BUCKET
          * @hide
          */
-        public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 0;
+        public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1;
 
         /**
          * Whether or not app auto restriction is enabled. When it is enabled, settings app will
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 374de9c..38945f5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4559,6 +4559,15 @@
         public static final String VOICE_REG_STATE = "voice_reg_state";
 
         /**
+         * An integer value indicating the current data service state.
+         * <p>
+         * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+         * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+         * {@link ServiceState#STATE_POWER_OFF}.
+         */
+        public static final String DATA_REG_STATE = "data_reg_state";
+
+        /**
          * The current registered operator numeric id.
          * <p>
          * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
@@ -4574,6 +4583,24 @@
          * This is the same as {@link ServiceState#getIsManualSelection()}.
          */
         public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+
+        /**
+         * The current data network type.
+         * <p>
+         * This is the same as {@link TelephonyManager#getDataNetworkType()}.
+         */
+        public static final String DATA_NETWORK_TYPE = "data_network_type";
+
+        /**
+         * An integer value indicating the current duplex mode if the radio technology is LTE,
+         * LTE-CA or NR.
+         * <p>
+         * Valid values: {@link ServiceState#DUPLEX_MODE_UNKNOWN},
+         * {@link ServiceState#DUPLEX_MODE_FDD}, {@link ServiceState#DUPLEX_MODE_TDD}.
+         * <p>
+         * This is the same as {@link ServiceState#getDuplexMode()}.
+         */
+        public static final String DUPLEX_MODE = "duplex_mode";
     }
 
     /**
@@ -5317,5 +5344,14 @@
          * @hide
          */
         public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
+
+        /**
+         * TelephonyProvider column name for information selected contacts that allow device to
+         * device sharing.
+         *
+         * @hide
+         */
+        public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS =
+                "d2d_sharing_contacts";
     }
 }
diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java
index 18cc29d..c7017b2 100644
--- a/core/java/android/service/translation/TranslationServiceInfo.java
+++ b/core/java/android/service/translation/TranslationServiceInfo.java
@@ -100,9 +100,9 @@
         if (!Manifest.permission.BIND_TRANSLATION_SERVICE.equals(si.permission)) {
             Slog.w(TAG, "TranslationServiceInfo from '" + si.packageName
                     + "' does not require permission "
-                    + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+                    + Manifest.permission.BIND_TRANSLATION_SERVICE);
             throw new SecurityException("Service does not require permission "
-                    + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+                    + Manifest.permission.BIND_TRANSLATION_SERVICE);
         }
 
         mServiceInfo = si;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index def13db..1ea40be 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -41,10 +41,10 @@
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
 import android.util.Slog;
@@ -239,18 +239,6 @@
     public @interface ModelParams {}
 
     /**
-     * Indicates that the given audio data is a false alert for {@link VoiceInteractionService}.
-     */
-    public static final int HOTWORD_DETECTION_FALSE_ALERT = 0;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, prefix = { "HOTWORD_DETECTION_" }, value = {
-            HOTWORD_DETECTION_FALSE_ALERT,
-    })
-    public @interface HotwordDetectionResult {}
-
-    /**
      * Controls the sensitivity threshold adjustment factor for a given model.
      * Negative value corresponds to less sensitive model (high threshold) and
      * a positive value corresponds to a more sensitive model (low threshold).
@@ -478,11 +466,14 @@
         public abstract void onRecognitionResumed();
 
         /**
-         * Called when the validated result is invalid.
+         * Called when the {@link HotwordDetectionService second stage detection} did not detect the
+         * keyphrase.
          *
-         * @param reason The reason why the validated result is invalid.
+         * @param result Info about the second stage detection result, provided by the
+         *         {@link HotwordDetectionService}.
          */
-        public void onRejected(@HotwordDetectionResult int reason) {}
+        public void onRejected(@Nullable HotwordRejectedResult result) {
+        }
     }
 
     /**
@@ -494,8 +485,8 @@
      * @param supportHotwordDetectionService {@code true} if hotword detection service should be
      * triggered, otherwise {@code false}.
      * @param options Application configuration data provided by the
-     * {@link VoiceInteractionService}. The system strips out any remotable objects or other
-     * contents that can be used to communicate with other processes.
+     * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
      * @param sharedMemory The unrestricted data blob provided by the
      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
@@ -505,7 +496,7 @@
     public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
             IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
-            boolean supportHotwordDetectionService, @Nullable Bundle options,
+            boolean supportHotwordDetectionService, @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) {
         mText = text;
         mLocale = locale;
@@ -534,8 +525,8 @@
      * Set configuration and pass read-only data to hotword detection service.
      *
      * @param options Application configuration data provided by the
-     * {@link VoiceInteractionService}. The system strips out any remotable objects or other
-     * contents that can be used to communicate with other processes.
+     * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
      * @param sharedMemory The unrestricted data blob provided by the
      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
@@ -544,7 +535,7 @@
      *
      * @hide
      */
-    public final void setHotwordDetectionServiceConfig(@Nullable Bundle options,
+    public final void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) {
         if (DBG) {
             Slog.d(TAG, "setHotwordDetectionServiceConfig()");
@@ -1069,13 +1060,13 @@
         }
 
         @Override
-        public void onRejected(int reason) {
+        public void onRejected(HotwordRejectedResult result) {
             if (DBG) {
-                Slog.d(TAG, "onRejected(" + reason + ")");
+                Slog.d(TAG, "onRejected(" + result + ")");
             } else {
                 Slog.i(TAG, "onRejected");
             }
-            Message.obtain(mHandler, MSG_HOTWORD_REJECTED, reason).sendToTarget();
+            Message.obtain(mHandler, MSG_HOTWORD_REJECTED, result).sendToTarget();
         }
 
         @Override
@@ -1124,7 +1115,7 @@
                     mExternalCallback.onRecognitionResumed();
                     break;
                 case MSG_HOTWORD_REJECTED:
-                    mExternalCallback.onRejected(msg.arg1);
+                    mExternalCallback.onRejected((HotwordRejectedResult) msg.obj);
                     break;
                 default:
                     super.handleMessage(msg);
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index fcef26f..686268c 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -27,11 +27,11 @@
 import android.app.Service;
 import android.content.Intent;
 import android.media.AudioFormat;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
 import android.util.Log;
@@ -82,7 +82,8 @@
         }
 
         @Override
-        public void setConfig(Bundle options, SharedMemory sharedMemory) throws RemoteException {
+        public void setConfig(PersistableBundle options, SharedMemory sharedMemory)
+                throws RemoteException {
             if (DBG) {
                 Log.d(TAG, "#setConfig");
             }
@@ -137,13 +138,13 @@
 
     /**
      * Called when the {@link VoiceInteractionService#createAlwaysOnHotwordDetector(String, Locale,
-     * Bundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or {@link AlwaysOnHotwordDetector#
-     * setHotwordDetectionServiceConfig(Bundle, SharedMemory)} requests an update of the hotword
-     * detection parameters.
+     * PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or
+     * {@link AlwaysOnHotwordDetector#setHotwordDetectionServiceConfig(PersistableBundle,
+     * SharedMemory)} requests an update of the hotword detection parameters.
      *
      * @param options Application configuration data provided by the
-     * {@link VoiceInteractionService}. The system strips out any remotable objects or other
-     * contents that can be used to communicate with other processes.
+     * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
      * @param sharedMemory The unrestricted data blob provided by the
      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
@@ -151,7 +152,8 @@
      * @hide
      */
     @SystemApi
-    public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) {
+    public void onUpdateState(@Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory) {
     }
 
     /**
@@ -180,11 +182,14 @@
         }
 
         /**
-         * Called when the detected result is invalid.
+         * Informs the {@link AlwaysOnHotwordDetector} that the keyphrase was not detected.
+         *
+         * @param result Info about the second stage detection result. This is provided to
+         *         the {@link AlwaysOnHotwordDetector}.
          */
-        public void onRejected() {
+        public void onRejected(@Nullable HotwordRejectedResult result) {
             try {
-                mRemoteCallback.onRejected();
+                mRemoteCallback.onRejected(result);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
index bb95bb8..6f641e1 100644
--- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
+++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import android.service.voice.HotwordRejectedResult;
+
 /**
  * Callback for returning the detected result from the HotwordDetectionService.
  *
@@ -28,7 +30,7 @@
     void onDetected();
 
     /**
-     * Called when the detected result is invalid.
+     * Sends {@code result} to the HotwordDetector.
      */
-    void onRejected();
+    void onRejected(in HotwordRejectedResult result);
 }
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl
index 8f0874a..8d01dd1 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/IHotwordDetectionService.aidl
@@ -17,8 +17,8 @@
 package android.service.voice;
 
 import android.media.AudioFormat;
-import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.SharedMemory;
 import android.service.voice.IDspHotwordDetectionCallback;
 
@@ -34,5 +34,5 @@
     long timeoutMillis,
     in IDspHotwordDetectionCallback callback);
 
-    void setConfig(in Bundle options, in SharedMemory sharedMemory);
+    void setConfig(in PersistableBundle options, in SharedMemory sharedMemory);
 }
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 9ba39a1..cb3791d 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -33,6 +33,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
@@ -342,8 +343,8 @@
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      * @param options Application configuration data provided by the
-     * {@link VoiceInteractionService}. The system strips out any remotable objects or other
-     * contents that can be used to communicate with other processes.
+     * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
      * @param sharedMemory The unrestricted data blob provided by the
      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
@@ -358,7 +359,7 @@
     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
-            @Nullable Bundle options,
+            @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
@@ -370,7 +371,7 @@
             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
             boolean supportHotwordDetectionService,
-            @Nullable Bundle options,
+            @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
         if (mSystemService == null) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 919c6e5..913ceae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
         DEFAULT_FLAGS.put("settings_silky_home", "true");
         DEFAULT_FLAGS.put("settings_contextual_home", "false");
         DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
-        DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false");
+        DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "false");
     }
 
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b0916d3..f2bc0c5 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -164,7 +164,7 @@
     }
 
     /**
-     * Returns the number of key-value mappings that this SparseIntArray
+     * Returns the number of key-value mappings that this SparseLongArray
      * currently stores.
      */
     public int size() {
@@ -246,7 +246,7 @@
     }
 
     /**
-     * Removes all key-value mappings from this SparseIntArray.
+     * Removes all key-value mappings from this SparseLongArray.
      */
     public void clear() {
         mSize = 0;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index a42126f..e1f13f2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -777,11 +777,11 @@
     VerifiedDisplayHash verifyDisplayHash(in DisplayHash displayHash);
 
     /**
-     * Registers a listener for a {@link android.app.WindowContext} to handle configuration changes
-     * from the server side.
+     * Registers a listener for a {@link android.window.WindowContext} to handle configuration
+     * changes from the server side.
      * <p>
      * Note that this API should be invoked after calling
-     * {@link android.app.WindowTokenClient#attachContext(WindowContext)}
+     * {@link android.window.WindowTokenClient#attachContext(Context)}
      * </p>
      *
      * @param clientToken the window context's token
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 870fd8c..11b161a 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -79,6 +79,26 @@
             mInputToken = inputToken;
         }
 
+        /**
+         * Constructs a copy of {@code SurfacePackage} with an independent lifetime.
+         *
+         * The caller can use this to create an independent copy in situations where ownership of
+         * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a
+         * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is
+         * responsible for releasing this copy when its done.
+         *
+         * @param other {@code SurfacePackage} to create a copy of.
+         */
+        public SurfacePackage(@NonNull SurfacePackage other) {
+            SurfaceControl otherSurfaceControl = other.mSurfaceControl;
+            if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
+                mSurfaceControl = new SurfaceControl();
+                mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+            }
+            mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
+            mInputToken = other.mInputToken;
+        }
+
         private SurfacePackage(Parcel in) {
             mSurfaceControl = new SurfaceControl();
             mSurfaceControl.readFromParcel(in);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9df87dc..93c3cab 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2942,7 +2942,7 @@
         public IBinder token = null;
 
         /**
-         * The token of {@link android.app.WindowContext}. It is usually a
+         * The token of {@link android.window.WindowContext}. It is usually a
          * {@link android.app.WindowTokenClient} and is used for associating the params with an
          * existing node in the WindowManager hierarchy and getting the corresponding
          * {@link Configuration} and {@link android.content.res.Resources} values with updates
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 8dce852..2bed311 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.window.WindowContext;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
@@ -110,7 +111,7 @@
         return new WindowManagerImpl(displayContext, mParentWindow, mWindowContextToken);
     }
 
-    /** Creates a {@link WindowManager} for a {@link android.app.WindowContext}. */
+    /** Creates a {@link WindowManager} for a {@link WindowContext}. */
     public static WindowManager createWindowContextWindowManager(Context context) {
         final IBinder clientToken = context.getWindowContextToken();
         return new WindowManagerImpl(context, null /* parentWindow */, clientToken);
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index d96c5c8..52e4e15 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -51,7 +51,7 @@
      * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
      * // Gets all excluding insets
      * final WindowInsets windowInsets = metrics.getWindowInsets();
-     * Insets insets = windowInsets.getInsetsIgnoreVisibility(WindowInsets.Type.navigationBars()
+     * Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
      *         | WindowInsets.Type.displayCutout());
      *
      * int insetsWidth = insets.right + insets.left;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3cd3902..6edd071 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2017,10 +2017,12 @@
             }
             mServedInputConnectionWrapper = servedContext;
 
-            try {
-                if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+            if (DEBUG) {
+                Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                         + ic + " tba=" + tba + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
+            }
+            try {
                 final Completable.InputBindResult value = Completable.createInputBindResult();
                 mService.startInputOrWindowGainedFocus(
                         startInputReason, mClient, windowGainingFocus, startInputFlags,
@@ -2028,37 +2030,37 @@
                         view.getContext().getApplicationInfo().targetSdkVersion,
                         ResultCallbacks.of(value));
                 res = Completable.getResult(value);
-                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
-                if (res == null) {
-                    Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
-                            + " null. startInputReason="
-                            + InputMethodDebug.startInputReasonToString(startInputReason)
-                            + " editorInfo=" + tba
-                            + " startInputFlags="
-                            + InputMethodDebug.startInputFlagsToString(startInputFlags));
-                    return false;
-                }
-                mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
-                mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
-                if (res.id != null) {
-                    setInputChannelLocked(res.channel);
-                    mBindSequence = res.sequence;
-                    mCurMethod = res.method; // for @UnsupportedAppUsage
-                    mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
-                    mCurId = res.id;
-                } else if (res.channel != null && res.channel != mCurChannel) {
-                    res.channel.dispose();
-                }
-                switch (res.result) {
-                    case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
-                        mRestartOnNextWindowFocus = true;
-                        break;
-                }
-                if (mCurrentInputMethodSession != null && mCompletions != null) {
-                    mCurrentInputMethodSession.displayCompletions(mCompletions);
-                }
             } catch (RemoteException e) {
-                Log.w(TAG, "IME died: " + mCurId, e);
+                throw e.rethrowFromSystemServer();
+            }
+            if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+            if (res == null) {
+                Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+                        + " null. startInputReason="
+                        + InputMethodDebug.startInputReasonToString(startInputReason)
+                        + " editorInfo=" + tba
+                        + " startInputFlags="
+                        + InputMethodDebug.startInputFlagsToString(startInputFlags));
+                return false;
+            }
+            mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
+            mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
+            if (res.id != null) {
+                setInputChannelLocked(res.channel);
+                mBindSequence = res.sequence;
+                mCurMethod = res.method; // for @UnsupportedAppUsage
+                mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
+                mCurId = res.id;
+            } else if (res.channel != null && res.channel != mCurChannel) {
+                res.channel.dispose();
+            }
+            switch (res.result) {
+                case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+                    mRestartOnNextWindowFocus = true;
+                    break;
+            }
+            if (mCurrentInputMethodSession != null && mCompletions != null) {
+                mCurrentInputMethodSession.displayCompletions(mCompletions);
             }
         }
 
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index dbc32e9..9c53f46 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -46,4 +46,5 @@
 
     void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
     void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
+    void getServiceSettingsActivity(in IResultReceiver result, int userId);
 }
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 66b45f3..dfa7095 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -35,6 +35,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.SyncResultReceiver;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -263,6 +264,31 @@
 
     //TODO: Add method to propagate updates to mTCapabilityUpdateListeners
 
+    /**
+     * Returns an immutable PendingIntent which can used by apps to launch translation settings.
+     *
+     * @return An immutable PendingIntent or {@code null} if one of reason met:
+     * <ul>
+     *     <li>Device manufacturer (OEM) does not provide TranslationService.</li>
+     *     <li>The TranslationService doesn't provide the Settings.</li>
+     * </ul>
+     **/
+    @Nullable
+    public PendingIntent getTranslationSettingsActivityIntent() {
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        try {
+            mService.getServiceSettingsActivity(resultReceiver, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        try {
+            return resultReceiver.getParcelableResult();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Fail to get translation service settings activity.");
+            return null;
+        }
+    }
+
     void removeTranslator(int id) {
         synchronized (mLock) {
             mTranslators.remove(id);
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 852ffe8..62868ac 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -127,10 +127,13 @@
      * @param destSpec {@link TranslationSpec} for the translated data.
      * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated
      * @param taskId the Activity Task id which needs ui translation
+     * @deprecated Use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>,
+     * ActivityId)} instead.
      *
      * @hide
+     * @removed
      */
-    // TODO, hide the APIs
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     @SystemApi
     public void startTranslation(@NonNull TranslationSpec sourceSpec,
@@ -193,10 +196,13 @@
      * NOTE: Please use {@code finishTranslation(ActivityId)} instead.
      *
      * @param taskId the Activity Task id which needs ui translation
+     * @deprecated Use {@code finishTranslation(ActivityId)} instead.
      *
      * @hide
+     * @removed
+     *
      */
-    // TODO, hide the APIs
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     @SystemApi
     public void finishTranslation(int taskId) {
@@ -240,10 +246,12 @@
      * NOTE: Please use {@code pauseTranslation(ActivityId)} instead.
      *
      * @param taskId the Activity Task id which needs ui translation
+     * @deprecated Use {@code pauseTranslation(ActivityId)} instead.
      *
      * @hide
+     * @removed
      */
-    // TODO, hide the APIs
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     @SystemApi
     public void pauseTranslation(int taskId) {
@@ -287,10 +295,12 @@
      * NOTE: Please use {@code resumeTranslation(ActivityId)} instead.
      *
      * @param taskId the Activity Task id which needs ui translation
+     * @deprecated Use {@code resumeTranslation(ActivityId)} instead.
      *
      * @hide
+     * @removed
      */
-    // TODO, hide the APIs
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
     @SystemApi
     public void resumeTranslation(int taskId) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 34ad659..1951194 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -130,11 +130,11 @@
     public @interface EdgeEffectType {
     }
 
-    private static final float LINEAR_STRETCH_INTENSITY = 0.03f;
+    private static final float LINEAR_STRETCH_INTENSITY = 0.06f;
 
-    private static final float EXP_STRETCH_INTENSITY = 0.02f;
+    private static final float EXP_STRETCH_INTENSITY = 0.06f;
 
-    private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.4f;
+    private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.33f;
 
     @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "EdgeEffect";
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/window/WindowContext.java
similarity index 61%
rename from core/java/android/app/WindowContext.java
rename to core/java/android/window/WindowContext.java
index d44918c..375f4cf 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app;
+package android.window;
 
 import static android.view.WindowManagerImpl.createWindowContextWindowManager;
 
@@ -27,11 +27,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.Display;
-import android.view.IWindowManager;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -49,11 +45,11 @@
 @UiContext
 public class WindowContext extends ContextWrapper {
     private final WindowManager mWindowManager;
-    private final IWindowManager mWms;
-    private final WindowTokenClient mToken;
-    private boolean mListenerRegistered;
+    private final @WindowManager.LayoutParams.WindowType int mType;
+    private final @Nullable Bundle mOptions;
     private final ComponentCallbacksController mCallbacksController =
             new ComponentCallbacksController();
+    private final WindowContextController mController;
 
     /**
      * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -64,47 +60,23 @@
      * @hide
      */
     public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
-        this(base, null /* display */, type, options);
-    }
+        super(base);
 
-    /**
-     * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
-     * the token.
-     *
-     * @param base Base {@link Context} for this new instance.
-     * @param display the {@link Display} to override.
-     * @param type Window type to be used with this context.
-     * @hide
-     */
-    public WindowContext(@NonNull Context base, @Nullable Display display, int type,
-            @Nullable Bundle options) {
-        // Correct base context will be built once the token is resolved, so passing 'null' here.
-        super(null /* base */);
-
-        mWms = WindowManagerGlobal.getWindowManagerService();
-        mToken = new WindowTokenClient();
-
-        final ContextImpl contextImpl = createBaseWindowContext(base, mToken, display);
-        attachBaseContext(contextImpl);
-        contextImpl.setOuterContext(this);
-
-        mToken.attachContext(this);
-
+        mType = type;
+        mOptions = options;
         mWindowManager = createWindowContextWindowManager(this);
+        IBinder token = getWindowContextToken();
+        mController = new WindowContextController(token);
 
-        try {
-            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(),
-                    options);
-        }  catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
         Reference.reachabilityFence(this);
     }
 
-    private static ContextImpl createBaseWindowContext(Context outer, IBinder token,
-            Display display) {
-        final ContextImpl contextImpl = ContextImpl.getImpl(outer);
-        return contextImpl.createBaseWindowContext(token, display);
+    /**
+     * Registers this {@link WindowContext} with {@link com.android.server.wm.WindowManagerService}
+     * to receive configuration changes of the associated {@link WindowManager} node.
+     */
+    public void registerWithServer() {
+        mController.registerListener(mType, getDisplayId(), mOptions);
     }
 
     @Override
@@ -124,21 +96,15 @@
     /** Used for test to invoke because we can't invoke finalize directly. */
     @VisibleForTesting
     public void release() {
-        if (mListenerRegistered) {
-            mListenerRegistered = false;
-            try {
-                mWms.unregisterWindowContextListener(mToken);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
+        mController.unregisterListenerIfNeeded();
         destroy();
     }
 
-    void destroy() {
+    @Override
+    public void destroy() {
         mCallbacksController.clearCallbacks();
-        final ContextImpl impl = (ContextImpl) getBaseContext();
-        impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
+        // Called to the base ContextImpl to do final clean-up.
+        getBaseContext().destroy();
         Reference.reachabilityFence(this);
     }
 
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
new file mode 100644
index 0000000..6143414
--- /dev/null
+++ b/core/java/android/window/WindowContextController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The controller to manage {@link WindowContext} listener, such as registering and unregistering
+ * the listener.
+ *
+ * @hide
+ */
+public class WindowContextController {
+    private final IWindowManager mWms;
+    @VisibleForTesting
+    public boolean mListenerRegistered;
+    @NonNull
+    private final IBinder mToken;
+
+    /**
+     * Window Context Controller constructor
+     *
+     * @param token The token to register to the window context listener. It is usually from
+     *              {@link Context#getWindowContextToken()}.
+     */
+    public WindowContextController(@NonNull IBinder token) {
+        mToken = token;
+        mWms = WindowManagerGlobal.getWindowManagerService();
+    }
+
+    /** Used for test only. DO NOT USE it in production code. */
+    @VisibleForTesting
+    public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+        mToken = token;
+        mWms = mockWms;
+    }
+
+    /**
+     * Registers the {@code mToken} to the window context listener.
+     *
+     * @param type The window type of the {@link WindowContext}
+     * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+     * @param options The window context launched option
+     */
+    public void registerListener(@WindowType int type, int displayId,  @Nullable Bundle options) {
+        if (mListenerRegistered) {
+            throw new UnsupportedOperationException("A Window Context can only register a listener"
+                    + " once.");
+        }
+        try {
+            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, displayId,
+                    options);
+        }  catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters the window context listener associated with the {@code mToken} if it has been
+     * registered.
+     */
+    public void unregisterListenerIfNeeded() {
+        if (mListenerRegistered) {
+            try {
+                mWms.unregisterWindowContextListener(mToken);
+                mListenerRegistered = false;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
similarity index 73%
rename from core/java/android/app/WindowTokenClient.java
rename to core/java/android/window/WindowTokenClient.java
index 82cef07..b2fe4d9 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -13,9 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app;
+package android.window;
 
 import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.app.IWindowToken;
+import android.app.ResourcesManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -25,18 +28,22 @@
 import java.lang.ref.WeakReference;
 
 /**
- * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from
- * server when window token config is updated or when it is moved between displays, and update the
- * resources associated with this token on the client side. This will make sure that
- * {@link WindowContext} instances will have updated resources and configuration.
+ * This class is used to receive {@link Configuration} changes from the associated window manager
+ * node on the server side, and apply the change to the {@link Context#getResources() associated
+ * Resources} of the attached {@link Context}. It is also used as
+ * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
+ *
+ * @see WindowContext
+ * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ *
  * @hide
  */
 public class WindowTokenClient extends IWindowToken.Stub {
     /**
      * Attached {@link Context} for this window token to update configuration and resources.
-     * Initialized by {@link #attachContext(WindowContext)}.
+     * Initialized by {@link #attachContext(Context)}.
      */
-    private WeakReference<WindowContext> mContextRef = null;
+    private WeakReference<Context> mContextRef = null;
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
@@ -50,18 +57,16 @@
      * @param context context to be attached
      * @throws IllegalStateException if attached context has already existed.
      */
-    void attachContext(@NonNull WindowContext context) {
+    public void attachContext(@NonNull Context context) {
         if (mContextRef != null) {
             throw new IllegalStateException("Context is already attached.");
         }
         mContextRef = new WeakReference<>(context);
-        final ContextImpl impl = ContextImpl.getImpl(context);
-        impl.setResources(impl.createWindowContextResources());
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
-        final WindowContext context = mContextRef.get();
+        final Context context = mContextRef.get();
         if (context == null) {
             return;
         }
@@ -72,8 +77,10 @@
         if (displayChanged || configChanged) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
-            ActivityThread.currentActivityThread().getHandler().post(
-                    () -> context.dispatchConfigurationChanged(newConfig));
+            if (context instanceof WindowContext) {
+                ActivityThread.currentActivityThread().getHandler().post(
+                        () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig));
+            }
         }
         if (displayChanged) {
             context.updateDisplay(newDisplayId);
@@ -82,7 +89,7 @@
 
     @Override
     public void onWindowTokenRemoved() {
-        final WindowContext context = mContextRef.get();
+        final Context context = mContextRef.get();
         if (context != null) {
             context.destroy();
             mContextRef.clear();
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 65bd841..23314e7 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import android.hardware.soundtrigger.SoundTrigger;
+import android.service.voice.HotwordRejectedResult;
 
 /**
  * @hide
@@ -41,11 +42,13 @@
     void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
 
     /**
-     * Called when the validated result is invalid.
+     * Called when the {@link HotwordDetectionService second stage detection} did not detect the
+     * keyphrase.
      *
-     * @param reason The reason why the validated result is invalid.
+     * @param result Info about the second stage detection result, provided by the
+     *         {@link HotwordDetectionService}.
      */
-    void onRejected(int reason);
+    void onRejected(in HotwordRejectedResult result);
 
     /**
      * Called when the detection fails due to an error.
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 592f7c7..2a022e6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.media.permission.Identity;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
 
@@ -229,13 +230,14 @@
      * Set configuration and pass read-only data to hotword detection service.
      *
      * @param options Application configuration data provided by the
-     * {@link VoiceInteractionService}. The system strips out any remotable objects or other
-     * contents that can be used to communicate with other processes.
+     * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
      * @param sharedMemory The unrestricted data blob provided by the
      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
      * such data to the trusted process.
      */
-    void setHotwordDetectionServiceConfig(in Bundle options, in SharedMemory sharedMemory);
+    void setHotwordDetectionServiceConfig(
+            in PersistableBundle options, in SharedMemory sharedMemory);
 
     /**
      * Requests to shutdown hotword detection service.
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index a5b894d..c7a36ee 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -374,11 +374,6 @@
      */
     public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
 
-    /**
-     * (boolean) Whether scrolling screenshots are enabled.
-     */
-    public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
-
     // Flags related to Nav Bar
 
     /**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a043756..ec0a8d8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1195,6 +1195,7 @@
 
     public BatteryStatsImpl(Clocks clocks) {
         init(clocks);
+        mStartClockTimeMs = System.currentTimeMillis();
         mStatsFile = null;
         mCheckinFile = null;
         mDailyFile = null;
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 6dd612e..4f99c94 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -134,7 +134,7 @@
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder =
                 new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount)
-                        .setStatsStartRealtime(mStats.getStatsStartRealtime() / 1000);
+                        .setStatsStartTimestamp(mStats.getStartClockTime());
 
         SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
         for (int i = uidStats.size() - 1; i >= 0; i--) {
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 16c3ab0..7b97524 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -25,6 +25,16 @@
    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
    optional int32 duration = 1;
    optional float amplitude = 2;
+   optional float frequency = 3;
+}
+
+message RampSegmentProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 duration = 1;
+    optional float startAmplitude = 2;
+    optional float endAmplitude = 3;
+    optional float startFrequency = 4;
+    optional float endFrequency = 5;
 }
 
 message PrebakedSegmentProto {
@@ -46,6 +56,7 @@
     optional PrebakedSegmentProto prebaked = 1;
     optional PrimitiveSegmentProto primitive = 2;
     optional StepSegmentProto step = 3;
+    optional RampSegmentProto ramp = 4;
 }
 
 // A com.android.os.VibrationEffect object.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4157af8..38ef9d2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1230,7 +1230,7 @@
         android:description="@string/permdesc_answerPhoneCalls"
         android:protectionLevel="dangerous|runtime" />
 
-    <!-- Allows a calling application which manages it own calls through the self-managed
+    <!-- Allows a calling application which manages its own calls through the self-managed
          {@link android.telecom.ConnectionService} APIs.  See
          {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the
          self-managed ConnectionService APIs.
@@ -1987,6 +1987,12 @@
     <permission android:name="android.permission.BLUETOOTH_STACK"
         android:protectionLevel="signature" />
 
+    <!-- Allows uhid write access for creating virtual input devices
+         @hide
+    -->
+    <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+        android:protectionLevel="signature" />
+
     <!-- Allows applications to perform I/O operations over NFC.
          <p>Protection level: normal
     -->
@@ -2963,6 +2969,15 @@
     <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
         android:protectionLevel="signature" />
 
+    <!-- Allows system clock time suggestions from an external clock / time source to be made.
+         The nature of "external" could be highly form-factor specific. Example, times
+         obtained via the VHAL for Android Auto OS.
+         <p>Not for use by third-party applications.
+         @SystemApi @hide
+    -->
+    <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows applications like settings to manage configuration associated with automatic time
          and time zone detection.
          <p>Not for use by third-party applications.
@@ -4935,6 +4950,11 @@
     <permission android:name="android.permission.SET_INITIAL_LOCK"
         android:protectionLevel="signature|setup"/>
 
+    <!-- @TestApi Allows applications to set and verify lockscreen credentials.
+        @hide -->
+    <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
+                android:protectionLevel="signature"/>
+
     <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_FINGERPRINT"
         android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml
index 3a8ad71..00d53004 100644
--- a/core/res/res/drawable/bottomsheet_background.xml
+++ b/core/res/res/drawable/bottomsheet_background.xml
@@ -18,5 +18,5 @@
     <corners
         android:topLeftRadius="@dimen/config_bottomDialogCornerRadius"
         android:topRightRadius="@dimen/config_bottomDialogCornerRadius"/>
-    <solid android:color="?attr/colorBackgroundFloating" />
+    <solid android:color="?attr/colorBackground" />
 </shape>
diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml
index 0dd9e9c7..18bbd93 100644
--- a/core/res/res/drawable/chooser_action_button_bg.xml
+++ b/core/res/res/drawable/chooser_action_button_bg.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/lighter_gray">
+        android:color="?android:attr/colorControlHighlight">
     <item>
         <inset
             android:insetLeft="0dp"
@@ -23,10 +23,8 @@
             android:insetRight="0dp"
             android:insetBottom="8dp">
             <shape android:shape="rectangle">
-              <corners android:radius="16dp"></corners>
-                <stroke android:width="1dp"
-                        android:color="?attr/opacityListDivider" />
-                <solid android:color="?attr/colorBackgroundFloating" />
+                <corners android:radius="16dp" />
+                <solid android:color="@color/system_neutral2_100" />
             </shape>
         </inset>
     </item>
diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml
index 6af7937..16ffaa4 100644
--- a/core/res/res/layout/chooser_action_button.xml
+++ b/core/res/res/layout/chooser_action_button.xml
@@ -25,6 +25,7 @@
     android:singleLine="true"
     android:clickable="true"
     android:background="@drawable/chooser_action_button_bg"
-    android:drawableTint="@color/chooser_chip_icon"
+    android:drawableTint="?android:textColorPrimary"
     android:drawableTintMode="src_in"
+    style="?android:attr/borderlessButtonStyle"
     />
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index c0de693..10683b1 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -67,7 +67,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_centerHorizontal="true"
-        android:background="?attr/colorBackgroundFloating">
+        android:background="?attr/colorBackground">
         <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
@@ -84,7 +84,7 @@
                 android:layout_alwaysShow="true"
                 android:layout_width="match_parent"
                 android:layout_height="1dp"
-                android:background="?attr/colorBackgroundFloating"
+                android:background="?attr/colorBackground"
                 android:foreground="?attr/dividerVertical" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml
index 2a39215..d8c1d17 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -24,7 +24,7 @@
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:paddingBottom="@dimen/chooser_view_spacing"
-    android:background="?attr/colorBackgroundFloating">
+    android:background="?attr/colorBackground">
 
   <LinearLayout
       android:layout_width="@dimen/chooser_preview_width"
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index 62df165..0d04d7f3 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -22,14 +22,14 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:background="?attr/colorBackgroundFloating">
+    android:background="?attr/colorBackground">
   <RelativeLayout
       android:id="@+id/content_preview_image_area"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center_horizontal"
       android:paddingBottom="@dimen/chooser_view_spacing"
-      android:background="?attr/colorBackgroundFloating">
+      android:background="?attr/colorBackground">
 
     <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
           android:id="@+id/content_preview_image_1_large"
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 1d18648..bc4f327 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -23,7 +23,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:background="?android:attr/colorBackgroundFloating">
+    android:background="?android:attr/colorBackground">
 
   <RelativeLayout
       android:layout_width="@dimen/chooser_preview_width"
diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml
index 86dc71c..912173c 100644
--- a/core/res/res/layout/chooser_list_per_profile.xml
+++ b/core/res/res/layout/chooser_list_per_profile.xml
@@ -23,7 +23,7 @@
         android:layoutManager="com.android.internal.app.ChooserGridLayoutManager"
         android:id="@+id/resolver_list"
         android:clipToPadding="false"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:scrollbars="none"
         android:elevation="1dp"
         android:nestedScrollingEnabled="true" />
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index bdcfeb2..8594c33 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -84,7 +84,7 @@
     <TextView android:id="@+id/empty"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:text="@string/noApplications"
         android:padding="@dimen/chooser_edge_margin_normal"
         android:layout_marginBottom="56dp"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6fde1df..d791598 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -68,7 +68,7 @@
         android:layout_alwaysShow="true"
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:foreground="?attr/dividerVertical" />
 
     <FrameLayout
@@ -76,7 +76,7 @@
         android:visibility="gone"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="?attr/colorBackgroundFloating"/>
+        android:background="?attr/colorBackground"/>
 
     <TabHost
         android:id="@+id/profile_tabhost"
@@ -85,7 +85,7 @@
         android:layout_alignParentTop="true"
         android:layout_centerHorizontal="true"
         android:accessibilityTraversalAfter="@id/title"
-        android:background="?attr/colorBackgroundFloating">
+        android:background="?attr/colorBackground">
         <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
@@ -101,7 +101,7 @@
                 android:visibility="gone"
                 android:layout_width="match_parent"
                 android:layout_height="1dp"
-                android:background="?attr/colorBackgroundFloating"
+                android:background="?attr/colorBackground"
                 android:foreground="?attr/dividerVertical"/>
             <FrameLayout
                 android:id="@android:id/tabcontent"
@@ -120,13 +120,13 @@
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
         android:orientation="vertical"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:layout_ignoreOffset="true">
         <View
             android:id="@+id/resolver_button_bar_divider"
             android:layout_width="match_parent"
             android:layout_height="1dp"
-            android:background="?attr/colorBackgroundFloating"
+            android:background="?attr/colorBackground"
             android:foreground="?attr/dividerVertical" />
         <LinearLayout
             android:id="@+id/button_bar"
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
index 9410301..d6ca7ab 100644
--- a/core/res/res/layout/resolver_list_per_profile.xml
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -24,7 +24,7 @@
         android:layout_height="wrap_content"
         android:id="@+id/resolver_list"
         android:clipToPadding="false"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:elevation="@dimen/resolver_elevation"
         android:nestedScrollingEnabled="true"
         android:scrollbarStyle="outsideOverlay"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 4a5aa02..7610e73 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -148,7 +148,7 @@
         android:layout_alwaysShow="true"
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:foreground="?attr/dividerVertical" />
 
     <FrameLayout
@@ -157,7 +157,7 @@
         android:visibility="gone"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="?attr/colorBackgroundFloating"/>
+        android:background="?attr/colorBackground"/>
 
     <TabHost
         android:layout_alwaysShow="true"
@@ -166,7 +166,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_centerHorizontal="true"
-        android:background="?attr/colorBackgroundFloating">
+        android:background="?attr/colorBackground">
         <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
@@ -182,7 +182,7 @@
                 android:visibility="gone"
                 android:layout_width="match_parent"
                 android:layout_height="1dp"
-                android:background="?attr/colorBackgroundFloating"
+                android:background="?attr/colorBackground"
                 android:foreground="?attr/dividerVertical"/>
             <FrameLayout
                 android:id="@android:id/tabcontent"
@@ -200,6 +200,6 @@
         android:layout_alwaysShow="true"
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:background="?attr/colorBackgroundFloating"
+        android:background="?attr/colorBackground"
         android:foreground="?attr/dividerVertical" />
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 4410e94..baffa5a 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -36,7 +36,6 @@
 
     <color name="resolver_empty_state_text">#FFFFFF</color>
     <color name="resolver_empty_state_icon">#FFFFFF</color>
-    <color name="chooser_chip_icon">#8AB4F8</color> <!-- Blue 300 -->
 
     <color name="personal_apps_suspension_notification_color">#8AB4F8</color>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5e95f94..4b15e01 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1125,6 +1125,15 @@
              to framework controls (via colorControlActivated). -->
         <attr name="colorAccent" format="color" />
 
+        <!-- Light accent color used on Material NEXT buttons. @hide -->
+        <attr name="colorAccentPrimary" format="color" />
+
+        <!-- Secondary accent color used on Material NEXT buttons. @hide -->
+        <attr name="colorAccentSecondary" format="color" />
+
+        <!-- Tertiary accent color used on Material NEXT buttons. @hide -->
+        <attr name="colorAccentTertiary" format="color" />
+
         <!-- The color applied to framework controls in their normal state. -->
         <attr name="colorControlNormal" format="color" />
 
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 140163e..38e8f83 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1853,6 +1853,25 @@
              -->
         <attr name="preserveLegacyExternalStorage" format="boolean" />
 
+        <!-- If {@code true} this app would like optimized external storage access.
+
+        <p> This flag can only be used by apps holding
+        <ul>
+        <li>{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission or
+        <li>{@link android.app.role}#SYSTEM_GALLERY role.
+        </ul>
+        When the flag is set, bulk file path operations will be optimized.
+
+        The default value is {@code true} if
+        <ul>
+        <li>app has {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission and
+        targets targetSDK<=30.
+        <li>app has {@link android.app.role}#SYSTEM_GALLERY role and targetSDK<=29
+        </ul>
+        {@code false} otherwise.
+        -->
+        <attr name="requestOptimizedExternalStorageAccess" format="boolean" />
+
         <!-- If {@code true} this app declares that it should be visible to all other apps on
              device, regardless of what they declare via the {@code queries} tags in their
              manifest.
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 748e3b1..0213c60 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -237,7 +237,6 @@
     <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
     <color name="resolver_empty_state_text">#FF202124</color>
     <color name="resolver_empty_state_icon">#FF5F6368</color>
-    <color name="chooser_chip_icon">#FF1A73E8</color> <!-- Blue 600 -->
 
     <!-- Color for personal app suspension notification button text and icon tint. -->
     <color name="personal_apps_suspension_notification_color">#1A73E8</color>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 3fbd7ca..0b41769 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -36,6 +36,9 @@
     <color name="accent_device_default_light">@color/system_accent1_600</color>
     <color name="accent_device_default_dark">@color/system_accent1_200</color>
     <color name="accent_device_default">@color/accent_device_default_light</color>
+    <color name="accent_primary_device_default">@color/system_accent1_100</color>
+    <color name="accent_secondary_device_default">@color/system_accent2_100</color>
+    <color name="accent_tertiary_device_default">@color/system_accent3_100</color>
 
     <color name="background_device_default_dark">@color/system_neutral1_800</color>
     <color name="background_device_default_light">@color/system_neutral1_50</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a3b3bb5..8df3221 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4580,6 +4580,11 @@
          check after reboot or airplane mode toggling -->
     <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
 
+    <!-- Boolean indicating whether all CB messages should be disabled on this device. This config
+         is intended to be used by OEMs who need to disable CB messages for regulatory requirements,
+         (e.g. the device is a tablet in a country where tablets should not receive CB messages) -->
+    <bool translatable="false" name="config_disable_all_cb_messages">false</bool>
+
     <!-- Screen Wake Keys
          Determines whether the specified key groups can be used to wake up the device. -->
     <bool name="config_wakeOnDpadKeyPress">true</bool>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7694faf..3259caf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3092,6 +3092,7 @@
     <public name="attributionTags"/>
     <public name="suppressesSpellChecker" />
     <public name="usesPermissionFlags" />
+    <public name="requestOptimizedExternalStorageAccess" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d6a6f4d..a704936 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -431,6 +431,7 @@
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
   <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+  <java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
   <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
   <java-symbol type="integer" name="config_veryLongPressTimeout" />
   <java-symbol type="integer" name="config_longPressOnBackBehavior" />
@@ -3954,6 +3955,7 @@
   <java-symbol type="layout" name="chooser_action_button" />
   <java-symbol type="dimen" name="chooser_action_button_icon_size" />
   <java-symbol type="string" name="config_defaultNearbySharingComponent" />
+  <java-symbol type="bool" name="config_disable_all_cb_messages" />
   <java-symbol type="drawable" name="ic_close" />
 
   <java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
@@ -4219,6 +4221,10 @@
 
   <java-symbol type="bool" name="config_enableOneHandedKeyguard" />
 
+  <java-symbol type="attr" name="colorAccentPrimary" />
+  <java-symbol type="attr" name="colorAccentSecondary" />
+  <java-symbol type="attr" name="colorAccentTertiary" />
+
   <!-- CEC Configuration -->
   <java-symbol type="bool" name="config_cecHdmiCecEnabled_userConfigurable" />
   <java-symbol type="bool" name="config_cecHdmiCecControlEnabled_allowed" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 16d720b..e40e31e 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -214,6 +214,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -237,6 +240,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -272,6 +278,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -309,6 +318,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -345,6 +357,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -396,6 +411,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -424,6 +442,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -458,6 +479,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -493,6 +517,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -544,6 +571,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -580,6 +610,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -614,6 +647,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -650,6 +686,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -685,6 +724,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -720,6 +762,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -755,6 +800,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -790,6 +838,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -829,6 +880,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -865,6 +919,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -898,6 +955,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -1085,6 +1145,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1104,6 +1167,9 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1138,6 +1204,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1173,6 +1242,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1210,6 +1282,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1246,6 +1321,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1299,6 +1377,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1326,6 +1407,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1363,6 +1447,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1401,6 +1488,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1440,6 +1530,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
         <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
@@ -1459,6 +1552,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
         <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
@@ -1477,6 +1573,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1516,6 +1615,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1553,6 +1655,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1589,6 +1694,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1624,6 +1732,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1659,6 +1770,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1692,6 +1806,9 @@
         <item name="colorPrimary">@color/primary_device_default_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1742,6 +1859,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorEdgeEffect">@color/edge_effect_device_default_light</item>
 
@@ -1772,6 +1892,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorControlNormal">?attr/textColorPrimary</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -1797,6 +1920,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
 
@@ -1815,6 +1941,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -1849,6 +1978,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1893,6 +2025,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1930,6 +2065,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -2030,10 +2168,16 @@
 
     <style name="ThemeOverlay.DeviceDefault.Accent">
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
     </style>
 
     <style name="ThemeOverlay.DeviceDefault.Accent.Light">
         <item name="colorAccent">@color/accent_device_default_light</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
     </style>
 
     <!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. -->
@@ -2042,6 +2186,9 @@
 
     <style name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" parent="ThemeOverlay.Material.Dark.ActionBar">
         <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen">
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index 6c28607..6a01abe 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="com.android.app.gamemanagertests"
           android:sharedUserId="android.uid.system" >
 
-    <application>
+    <application android:appCategory="game">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index 0c96411..baecc8c 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -37,9 +37,6 @@
 @SmallTest
 @Presubmit
 public final class GameManagerTests {
-    private static final String PACKAGE_NAME_0 = "com.android.app0";
-    private static final String PACKAGE_NAME_1 = "com.android.app1";
-
     protected Context mContext;
     private GameManager mGameManager;
     private String mPackageName;
@@ -52,8 +49,6 @@
 
         // Reset the Game Mode for the test app, since it persists across invocations.
         mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED);
-        mGameManager.setGameMode(PACKAGE_NAME_0, GameManager.GAME_MODE_UNSUPPORTED);
-        mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_UNSUPPORTED);
     }
 
     @Test
@@ -73,14 +68,14 @@
     @Test
     public void testPrivilegedGameModeGetterSetter() {
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                mGameManager.getGameMode(PACKAGE_NAME_0));
+                mGameManager.getGameMode(mPackageName));
 
-        mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD);
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
         assertEquals(GameManager.GAME_MODE_STANDARD,
-                mGameManager.getGameMode(PACKAGE_NAME_1));
+                mGameManager.getGameMode(mPackageName));
 
-        mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE);
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE);
         assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                mGameManager.getGameMode(PACKAGE_NAME_1));
+                mGameManager.getGameMode(mPackageName));
     }
 }
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 7cb6804..36da927 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -235,6 +235,7 @@
                 .setStatuses(statusList).setNotificationKey("key")
                 .setNotificationContent("content")
                 .setNotificationDataUri(Uri.parse("data"))
+                .setMessagesCount(2)
                 .setIntent(new Intent())
                 .build();
 
@@ -256,6 +257,7 @@
         assertThat(readTile.getNotificationKey()).isEqualTo(tile.getNotificationKey());
         assertThat(readTile.getNotificationContent()).isEqualTo(tile.getNotificationContent());
         assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri());
+        assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount());
         assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString());
     }
 
@@ -291,6 +293,17 @@
     }
 
     @Test
+    public void testMessagesCount() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(),
+                        mLauncherApps)
+                        .setMessagesCount(2)
+                        .build();
+
+        assertThat(tile.getMessagesCount()).isEqualTo(2);
+    }
+
+    @Test
     public void testIntent() {
         PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
                 new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index c61a4b5..0b94589 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -2,3 +2,5 @@
 per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
 per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksControllerTest = charlesccchen@google.com
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 242adab..009665f 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -118,7 +118,16 @@
     public void testValidateWaveform() {
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
+        VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 1, /* duration= */ 10)
+                .addRamp(/* amplitude= */ 0, /* duration= */ 20)
+                .addStep(/* amplitude= */ 1, /* frequency*/ 1, /* duration= */ 100)
+                .addRamp(/* amplitude= */ 0.5f, /* frequency*/ -1, /* duration= */ 50)
+                .build()
+                .validate();
 
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startWaveform().build().validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate());
         assertThrows(IllegalArgumentException.class,
@@ -132,17 +141,31 @@
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createWaveform(
                         TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveform()
+                        .addStep(/* amplitude= */ -2, 10).build().validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveform()
+                        .addStep(1, /* duration= */ -1).build().validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveform()
+                        .addStep(1, 0, /* duration= */ -1).build().validate());
     }
 
     @Test
     public void testValidateComposed() {
         VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addEffect(TEST_ONE_SHOT)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addEffect(TEST_WAVEFORM, 100)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose()
                 .validate();
 
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startComposition().compose().validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate());
         assertThrows(IllegalArgumentException.class,
@@ -152,6 +175,16 @@
                         .validate());
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, -10)
+                        .compose()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startComposition()
+                        .addEffect(TEST_ONE_SHOT, /* delay= */ -10)
+                        .compose()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startComposition()
                         .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
                         .compose()
                         .validate());
@@ -185,6 +218,12 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
                 .compose();
         assertEquals(effect, effect.resolve(51));
+
+        VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+                .addEffect(DEFAULT_ONE_SHOT)
+                .compose()
+                .resolve(51);
+        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
     }
 
     @Test
@@ -215,6 +254,13 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
                 .compose();
         assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT));
+
+        VibrationEffect.Composed applied = VibrationEffect.startComposition()
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .compose()
+                .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+                ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
     }
 
     @Test
@@ -251,13 +297,16 @@
         VibrationEffect.Composed effect =
                 (VibrationEffect.Composed) VibrationEffect.startComposition()
                         .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+                        .addEffect(TEST_ONE_SHOT)
                         .compose();
 
         VibrationEffect.Composed scaledUp = effect.scale(1.5f);
         assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
+        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
 
         VibrationEffect.Composed scaledDown = effect.scale(0.5f);
         assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
+        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
     }
 
     private Resources mockRingtoneResources() {
@@ -293,4 +342,4 @@
 
         return context;
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index 09c36dd..40fc00a 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -23,6 +23,7 @@
 
 import android.hardware.vibrator.IVibrator;
 import android.platform.test.annotations.Presubmit;
+import android.util.Range;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,6 +32,20 @@
 @Presubmit
 @RunWith(JUnit4.class)
 public class VibratorInfoTest {
+    private static final float TEST_TOLERANCE = 1e-5f;
+
+    private static final float TEST_MIN_FREQUENCY = 50;
+    private static final float TEST_RESONANT_FREQUENCY = 150;
+    private static final float TEST_FREQUENCY_RESOLUTION = 25;
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+    private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
+            new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
+    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
+            new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY,
+                    TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION,
+                    /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP);
 
     @Test
     public void testHasAmplitudeControl() {
@@ -83,6 +98,139 @@
     }
 
     @Test
+    public void testGetFrequencyRange_invalidFrequencyMappingReturnsEmptyRange() {
+        // Invalid, contains NaN values or empty array.
+        assertEquals(Range.create(0f, 0f), new InfoBuilder().build().getFrequencyRange());
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        Float.NaN, 150, 25, 50, TEST_AMPLITUDE_MAP))
+                .build().getFrequencyRange());
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        50, Float.NaN, 25, 50, TEST_AMPLITUDE_MAP))
+                .build().getFrequencyRange());
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        50, 150, Float.NaN, 50, TEST_AMPLITUDE_MAP))
+                .build().getFrequencyRange());
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        50, 150, 25, Float.NaN, TEST_AMPLITUDE_MAP))
+                .build().getFrequencyRange());
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(50, 150, 25, 50, null))
+                .build().getFrequencyRange());
+        // Invalid, minFrequency > resonantFrequency
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        /* minFrequencyHz= */ 250, /* resonantFrequency= */ 150, 25, 50, null))
+                .build().getFrequencyRange());
+        // Invalid, maxFrequency < resonantFrequency by changing resolution.
+        assertEquals(Range.create(0f, 0f), new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        50, 150, /* frequencyResolutionHz= */10, 50, null))
+                .build().getFrequencyRange());
+    }
+
+    @Test
+    public void testGetFrequencyRange_safeRangeLimitedByMaxFrequency() {
+        VibratorInfo info = new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+                        /* frequencyResolutionHz= */ 25, /* suggestedSafeRangeHz= */ 200,
+                        TEST_AMPLITUDE_MAP))
+                .build();
+
+        // Mapping should range from 50Hz = -2 to 200Hz = 1
+        // Safe range [-1, 1] = [100Hz, 200Hz] defined by max - resonant = 50Hz
+        assertEquals(Range.create(-2f, 1f), info.getFrequencyRange());
+    }
+
+    @Test
+    public void testGetFrequencyRange_safeRangeLimitedByMinFrequency() {
+        VibratorInfo info = new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+                        /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 200,
+                        TEST_AMPLITUDE_MAP))
+                .build();
+
+        // Mapping should range from 50Hz = -1 to 350Hz = 2
+        // Safe range [-1, 1] = [50Hz, 250Hz] defined by resonant - min = 100Hz
+        assertEquals(Range.create(-1f, 2f), info.getFrequencyRange());
+    }
+
+    @Test
+    public void testGetFrequencyRange_validMappingReturnsFullRelativeRange() {
+        VibratorInfo info = new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+                        /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+                        /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 100,
+                        TEST_AMPLITUDE_MAP))
+                .build();
+
+        // Mapping should range from 50Hz = -2 to 350Hz = 4
+        // Safe range [-1, 1] = [100Hz, 200Hz] defined by suggested safe range 100Hz
+        assertEquals(Range.create(-2f, 4f), info.getFrequencyRange());
+    }
+
+    @Test
+    public void testAbsoluteFrequency_emptyMappingReturnsNaN() {
+        VibratorInfo info = new InfoBuilder().build();
+        assertTrue(Float.isNaN(info.getAbsoluteFrequency(-1)));
+        assertTrue(Float.isNaN(info.getAbsoluteFrequency(0)));
+        assertTrue(Float.isNaN(info.getAbsoluteFrequency(1)));
+    }
+
+    @Test
+    public void testAbsoluteFrequency_validRangeReturnsOriginalValue() {
+        VibratorInfo info = new InfoBuilder().setFrequencyMapping(TEST_FREQUENCY_MAPPING).build();
+        assertEquals(TEST_RESONANT_FREQUENCY, info.getAbsoluteFrequency(0), TEST_TOLERANCE);
+
+        // Safe range [-1, 1] = [125Hz, 175Hz] defined by suggested safe range 100Hz
+        assertEquals(125, info.getAbsoluteFrequency(-1), TEST_TOLERANCE);
+        assertEquals(175, info.getAbsoluteFrequency(1), TEST_TOLERANCE);
+        assertEquals(155, info.getAbsoluteFrequency(0.2f), TEST_TOLERANCE);
+        assertEquals(140, info.getAbsoluteFrequency(-0.4f), TEST_TOLERANCE);
+
+        // Full range [-4, 2] = [50Hz, 200Hz] defined by min frequency and amplitude mapping size
+        assertEquals(50, info.getAbsoluteFrequency(info.getFrequencyRange().getLower()),
+                TEST_TOLERANCE);
+        assertEquals(200, info.getAbsoluteFrequency(info.getFrequencyRange().getUpper()),
+                TEST_TOLERANCE);
+    }
+
+    @Test
+    public void testGetMaxAmplitude_emptyMappingReturnsOnlyResonantFrequency() {
+        VibratorInfo info = new InfoBuilder().build();
+        assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE);
+        assertEquals(0f, info.getMaxAmplitude(0.1f), TEST_TOLERANCE);
+        assertEquals(0f, info.getMaxAmplitude(-1), TEST_TOLERANCE);
+    }
+
+    @Test
+    public void testGetMaxAmplitude_validMappingReturnsMappedValues() {
+        VibratorInfo info = new InfoBuilder()
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(/* minFrequencyHz= */ 50,
+                        /* resonantFrequencyHz= */ 150, /* frequencyResolutionHz= */ 25,
+                        /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP))
+                .build();
+
+        assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE); // 150Hz
+        assertEquals(0.9f, info.getMaxAmplitude(1), TEST_TOLERANCE); // 175Hz
+        assertEquals(0.8f, info.getMaxAmplitude(-1), TEST_TOLERANCE); // 125Hz
+        assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRange().getUpper()),
+                TEST_TOLERANCE); // 200Hz
+        assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRange().getLower()),
+                TEST_TOLERANCE); // 50Hz
+
+        // Rounds 145Hz to the max amplitude for 125Hz, which is lower.
+        assertEquals(0.8f, info.getMaxAmplitude(-0.1f), TEST_TOLERANCE); // 145Hz
+        // Rounds 185Hz to the max amplitude for 200Hz, which is lower.
+        assertEquals(0.8f, info.getMaxAmplitude(1.2f), TEST_TOLERANCE); // 185Hz
+    }
+
+    @Test
     public void testEquals() {
         InfoBuilder completeBuilder = new InfoBuilder()
                 .setId(1)
@@ -90,7 +238,7 @@
                 .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                 .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .setQFactor(2f)
-                .setResonantFrequency(150f);
+                .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
         VibratorInfo complete = completeBuilder.build();
 
         assertEquals(complete, complete);
@@ -110,22 +258,24 @@
         VibratorInfo completeWithUnknownEffects = completeBuilder
                 .setSupportedEffects(null)
                 .build();
-        assertNotEquals(complete, completeWithNoEffects);
+        assertNotEquals(complete, completeWithUnknownEffects);
 
         VibratorInfo completeWithUnknownPrimitives = completeBuilder
                 .setSupportedPrimitives(null)
                 .build();
         assertNotEquals(complete, completeWithUnknownPrimitives);
 
-        VibratorInfo completeWithDifferentF0 = completeBuilder
-                .setResonantFrequency(complete.getResonantFrequency() + 3f)
+        VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
+                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY + 10,
+                        TEST_RESONANT_FREQUENCY + 20, TEST_FREQUENCY_RESOLUTION + 5,
+                        /* suggestedSafeRangeHz= */ 100, TEST_AMPLITUDE_MAP))
                 .build();
-        assertNotEquals(complete, completeWithDifferentF0);
+        assertNotEquals(complete, completeWithDifferentFrequencyMapping);
 
-        VibratorInfo completeWithUnknownF0 = completeBuilder
-                .setResonantFrequency(Float.NaN)
+        VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder
+                .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING)
                 .build();
-        assertNotEquals(complete, completeWithUnknownF0);
+        assertNotEquals(complete, completeWithEmptyFrequencyMapping);
 
         VibratorInfo completeWithUnknownQFactor = completeBuilder
                 .setQFactor(Float.NaN)
@@ -153,8 +303,8 @@
                 .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                 .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                 .setSupportedPrimitives(null)
-                .setResonantFrequency(1.3f)
                 .setQFactor(Float.NaN)
+                .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
                 .build();
 
         Parcel parcel = Parcel.obtain();
@@ -169,8 +319,8 @@
         private int mCapabilities = 0;
         private int[] mSupportedEffects = null;
         private int[] mSupportedPrimitives = null;
-        private float mResonantFrequency = Float.NaN;
         private float mQFactor = Float.NaN;
+        private VibratorInfo.FrequencyMapping mFrequencyMapping = EMPTY_FREQUENCY_MAPPING;
 
         public InfoBuilder setId(int id) {
             mId = id;
@@ -192,19 +342,19 @@
             return this;
         }
 
-        public InfoBuilder setResonantFrequency(float resonantFrequency) {
-            mResonantFrequency = resonantFrequency;
-            return this;
-        }
-
         public InfoBuilder setQFactor(float qFactor) {
             mQFactor = qFactor;
             return this;
         }
 
+        public InfoBuilder setFrequencyMapping(VibratorInfo.FrequencyMapping frequencyMapping) {
+            mFrequencyMapping = frequencyMapping;
+            return this;
+        }
+
         public VibratorInfo build() {
             return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
-                    mResonantFrequency, mQFactor);
+                    mQFactor, mFrequencyMapping);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
new file mode 100644
index 0000000..174b4a7
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class RampSegmentTest {
+    private static final float TOLERANCE = 1e-2f;
+
+    @Test
+    public void testCreation() {
+        RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100);
+
+        assertEquals(100L, ramp.getDuration());
+        assertTrue(ramp.hasNonZeroAmplitude());
+        assertEquals(1f, ramp.getStartAmplitude());
+        assertEquals(0f, ramp.getEndAmplitude());
+        assertEquals(-1f, ramp.getStartFrequency());
+        assertEquals(1f, ramp.getEndFrequency());
+    }
+
+    @Test
+    public void testSerialization() {
+        RampSegment original = new RampSegment(0, 1, 0, 0.5f, 10);
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(original, RampSegment.CREATOR.createFromParcel(parcel));
+    }
+
+    @Test
+    public void testValidate() {
+        new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100).validate();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new RampSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0, 0, 0).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new RampSegment(/* startAmplitude= */ -2, 0, 0, 0, 0).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new RampSegment(0, /* endAmplitude= */ 2, 0, 0, 0).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new RampSegment(0, 0, 0, 0, /* duration= */ -1).validate());
+    }
+
+    @Test
+    public void testHasNonZeroAmplitude() {
+        assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude());
+        assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude());
+        assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude());
+    }
+
+    @Test
+    public void testResolve() {
+        RampSegment ramp = new RampSegment(0, 1, 0, 0, 0);
+        assertSame(ramp, ramp.resolve(100));
+    }
+
+    @Test
+    public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+        RampSegment ramp = new RampSegment(1, 0, 1, 0, 0);
+        assertSame(ramp, ramp.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+    }
+
+    @Test
+    public void testScale() {
+        RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+        assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+
+        assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.71f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE);
+        assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_halfPrimitiveScaleValue() {
+        RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
+
+        assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
index 188b6c1..79529b8 100644
--- a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -38,16 +38,18 @@
 
     @Test
     public void testCreation() {
-        StepSegment step = new StepSegment(/* amplitude= */ 1f, /* duration= */ 100);
+        StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequency= */ -1f,
+                /* duration= */ 100);
 
         assertEquals(100, step.getDuration());
         assertTrue(step.hasNonZeroAmplitude());
         assertEquals(1f, step.getAmplitude());
+        assertEquals(-1f, step.getFrequency());
     }
 
     @Test
     public void testSerialization() {
-        StepSegment original = new StepSegment(0.5f, 10);
+        StepSegment original = new StepSegment(0.5f, 1f, 10);
         Parcel parcel = Parcel.obtain();
         original.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -56,31 +58,31 @@
 
     @Test
     public void testValidate() {
-        new StepSegment(/* amplitude= */ 0f, /* duration= */ 100).validate();
+        new StepSegment(/* amplitude= */ 0f, /* frequency= */ -1f, /* duration= */ 100).validate();
 
         assertThrows(IllegalArgumentException.class,
-                () -> new StepSegment(/* amplitude= */ -2, 10).validate());
+                () -> new StepSegment(/* amplitude= */ -2, 1f, 10).validate());
         assertThrows(IllegalArgumentException.class,
-                () -> new StepSegment(/* amplitude= */ 2, 10).validate());
+                () -> new StepSegment(/* amplitude= */ 2, 1f, 10).validate());
         assertThrows(IllegalArgumentException.class,
-                () -> new StepSegment(2, /* duration= */ -1).validate());
+                () -> new StepSegment(2, 1f, /* duration= */ -1).validate());
     }
 
     @Test
     public void testHasNonZeroAmplitude() {
-        assertTrue(new StepSegment(1f, 0).hasNonZeroAmplitude());
-        assertTrue(new StepSegment(0.01f, 0).hasNonZeroAmplitude());
-        assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0).hasNonZeroAmplitude());
-        assertFalse(new StepSegment(0, 0).hasNonZeroAmplitude());
+        assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude());
+        assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude());
+        assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude());
+        assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude());
     }
 
     @Test
     public void testResolve() {
-        StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+        StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
         assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
         assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE);
 
-        StepSegment resolved = new StepSegment(0, 0);
+        StepSegment resolved = new StepSegment(0, 0, 0);
         assertSame(resolved, resolved.resolve(100));
 
         assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000));
@@ -88,13 +90,13 @@
 
     @Test
     public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
-        StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+        StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
         assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
     }
 
     @Test
     public void testScale_fullAmplitude() {
-        StepSegment initial = new StepSegment(1f, 0);
+        StepSegment initial = new StepSegment(1f, 0, 0);
 
         assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
         assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
@@ -108,7 +110,7 @@
 
     @Test
     public void testScale_halfAmplitude() {
-        StepSegment initial = new StepSegment(0.5f, 0);
+        StepSegment initial = new StepSegment(0.5f, 0, 0);
 
         assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
         assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
@@ -122,7 +124,7 @@
 
     @Test
     public void testScale_zeroAmplitude() {
-        StepSegment initial = new StepSegment(0, 0);
+        StepSegment initial = new StepSegment(0, 0, 0);
 
         assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE);
         assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
@@ -131,7 +133,7 @@
 
     @Test
     public void testScale_defaultAmplitude() {
-        StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+        StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
 
         assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE);
         assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(),
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index 96df9dd..39ea8af 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -73,17 +73,17 @@
             // Check get metrics do not crash.
             WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
             WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics();
-            verifyMetricsSanity(currentMetrics, maxMetrics);
+            verifyMetricsValidity(currentMetrics, maxMetrics);
 
             mWm.removeViewImmediate(view);
             // Check get metrics do not crash.
             currentMetrics = mWm.getCurrentWindowMetrics();
             maxMetrics = mWm.getMaximumWindowMetrics();
-            verifyMetricsSanity(currentMetrics, maxMetrics);
+            verifyMetricsValidity(currentMetrics, maxMetrics);
         }, 0);
     }
 
-    private static void verifyMetricsSanity(WindowMetrics currentMetrics,
+    private static void verifyMetricsValidity(WindowMetrics currentMetrics,
             WindowMetrics maxMetrics) {
         Rect currentBounds = currentMetrics.getBounds();
         Rect maxBounds = maxMetrics.getBounds();
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
new file mode 100644
index 0000000..e4fc19c
--- /dev/null
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContextController}
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowContextControllerTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextControllerTest {
+    private WindowContextController mController;
+    private IWindowManager mMockWms;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockWms = mock(IWindowManager.class);
+        mController = new WindowContextController(new Binder(), mMockWms);
+
+        doReturn(true).when(mMockWms).registerWindowContextListener(
+                any(), anyInt(), anyInt(), any());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRegisterListenerTwiceThrowException() {
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+    }
+
+    @Test
+    public void testUnregisterListenerIfNeeded_NotRegisteredYet_DoNothing() throws Exception {
+        mController.unregisterListenerIfNeeded();
+
+        verify(mMockWms, never()).registerWindowContextListener(any(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testRegisterAndUnRegisterListener() {
+        mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+                null /* options */);
+
+        assertThat(mController.mListenerRegistered).isTrue();
+
+        mController.unregisterListenerIfNeeded();
+
+        assertThat(mController.mListenerRegistered).isFalse();
+    }
+}
diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
similarity index 97%
rename from core/tests/coretests/src/android/app/WindowContextTest.java
rename to core/tests/coretests/src/android/window/WindowContextTest.java
index 48b58c6..614e7c1 100644
--- a/core/tests/coretests/src/android/app/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.window;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -24,6 +24,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.app.Activity;
+import android.app.EmptyActivity;
+import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -142,8 +145,8 @@
      * {@link WindowManager.LayoutParams#token}.
      *
      * The window context token should be overridden to
-     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must
-     * not be removed regardless of the release of window context.
+     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be
+     * removed regardless of release of window context.
      */
     @Test
     public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease()
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 33b8aed..fc3be13 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -68,7 +68,8 @@
 
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1)
                 .setDischargePercentage(20)
-                .setDischargedPowerRange(1000, 2000);
+                .setDischargedPowerRange(1000, 2000)
+                .setStatsStartTimestamp(1000);
 
         builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid)
                 .setPackageWithHighestDrain("foo")
@@ -105,6 +106,7 @@
         assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20);
         assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000);
         assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000);
+        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(1000);
 
         final List<UidBatteryConsumer> uidBatteryConsumers =
                 batteryUsageStats.getUidBatteryConsumers();
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 8da4a37e..3213390 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -48,6 +48,10 @@
         <group gid="uhid" />
     </permission>
 
+    <permission name="android.permission.VIRTUAL_INPUT_DEVICE" >
+        <group gid="uhid" />
+    </permission>
+
     <permission name="android.permission.NET_TUNNELING" >
         <group gid="vpn" />
     </permission>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 2a6bbf3..cafda09 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1441,13 +1441,11 @@
 
     /** @hide */
     public boolean isSupportedAxes(int axis) {
-        if (mSupportedAxes == null) {
-            synchronized (this) {
+        synchronized (this) {
+            if (mSupportedAxes == null) {
+                mSupportedAxes = nativeGetSupportedAxes(native_instance);
                 if (mSupportedAxes == null) {
-                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
-                    if (mSupportedAxes == null) {
-                        mSupportedAxes = EMPTY_AXES;
-                    }
+                    mSupportedAxes = EMPTY_AXES;
                 }
             }
         }
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index cd77d9c..ec3b102 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -254,6 +254,8 @@
             keyStore.deleteEntry(keystoreAlias);
 
             return certificateChain;
+        } catch (SecurityException e) {
+            throw e;
         } catch (Exception e) {
             throw new DeviceIdAttestationException("Unable to perform attestation", e);
         }
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 5cb2c3b..9ca551b 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -288,7 +288,7 @@
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
 
     private final String mKeystoreAlias;
-    private final int mNamespace;
+    private final @KeyProperties.Namespace int mNamespace;
     private final int mKeySize;
     private final AlgorithmParameterSpec mSpec;
     private final X500Principal mCertificateSubject;
@@ -331,7 +331,7 @@
      */
     public KeyGenParameterSpec(
             String keyStoreAlias,
-            int namespace,
+            @KeyProperties.Namespace int namespace,
             int keySize,
             AlgorithmParameterSpec spec,
             X500Principal certificateSubject,
@@ -472,7 +472,7 @@
      * @hide
      */
     @SystemApi
-    public int getNamespace() {
+    public @KeyProperties.Namespace int getNamespace() {
         return mNamespace;
     }
 
@@ -896,7 +896,7 @@
         private final String mKeystoreAlias;
         private @KeyProperties.PurposeEnum int mPurposes;
 
-        private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+        private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
         private int mKeySize = -1;
         private AlgorithmParameterSpec mSpec;
         private X500Principal mCertificateSubject;
@@ -1051,7 +1051,7 @@
          */
         @SystemApi
         @NonNull
-        public Builder setNamespace(int namespace) {
+        public Builder setNamespace(@KeyProperties.Namespace int namespace) {
             mNamespace = namespace;
             return this;
         }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 7b0fa91..682d12a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -892,6 +892,22 @@
     }
 
     /**
+     * Namespaces provide system developers and vendors with a way to use keystore without
+     * requiring an applications uid. Namespaces can be configured using SEPolicy.
+     * See <a href="https://source.android.com/security/keystore#access-control">
+     *     Keystore 2.0 access-control</a>
+     * {@See KeyGenParameterSpec.Builder#setNamespace}
+     * {@See android.security.keystore2.AndroidKeyStoreLoadStoreParameter}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NAMESPACE_" }, value = {
+            NAMESPACE_APPLICATION,
+            NAMESPACE_WIFI,
+    })
+    public @interface Namespace {}
+
+    /**
      * This value indicates the implicit keystore namespace of the calling application.
      * It is used by default. Only select system components can choose a different namespace
      * which it must be configured in SEPolicy.
@@ -912,14 +928,12 @@
      * For legacy support, translate namespaces into known UIDs.
      * @hide
      */
-    public static int namespaceToLegacyUid(int namespace) {
+    public static int namespaceToLegacyUid(@Namespace int namespace) {
         switch (namespace) {
             case NAMESPACE_APPLICATION:
                 return KeyStore.UID_SELF;
             case NAMESPACE_WIFI:
                 return Process.WIFI_UID;
-            // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
-            //  b/171305388 and b/171305607
             default:
                 throw new IllegalArgumentException("No UID corresponding to namespace "
                         + namespace);
@@ -930,14 +944,12 @@
      * For legacy support, translate namespaces into known UIDs.
      * @hide
      */
-    public static int legacyUidToNamespace(int uid) {
+    public static @Namespace int legacyUidToNamespace(int uid) {
         switch (uid) {
             case KeyStore.UID_SELF:
                 return NAMESPACE_APPLICATION;
             case Process.WIFI_UID:
                 return NAMESPACE_WIFI;
-            // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
-            //  b/171305388 and b/171305607
             default:
                 throw new IllegalArgumentException("No namespace corresponding to uid "
                         + uid);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
index 0c6744f..25b1c86 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
@@ -16,6 +16,8 @@
 
 package android.security.keystore2;
 
+import android.security.keystore.KeyProperties;
+
 import java.security.KeyStore;
 import java.security.KeyStore.ProtectionParameter;
 
@@ -24,9 +26,9 @@
  */
 public class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
 
-    private final int mNamespace;
+    private final @KeyProperties.Namespace int mNamespace;
 
-    public AndroidKeyStoreLoadStoreParameter(int namespace) {
+    public AndroidKeyStoreLoadStoreParameter(@KeyProperties.Namespace int namespace) {
         mNamespace = namespace;
     }
 
@@ -35,7 +37,7 @@
         return null;
     }
 
-    int getNamespace() {
+    @KeyProperties.Namespace int getNamespace() {
         return mNamespace;
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 39607ae..32f98a2 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -100,7 +100,7 @@
     public static final String NAME = "AndroidKeyStore";
 
     private KeyStore2 mKeyStore;
-    private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+    private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
 
     @Override
     public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
@@ -1125,7 +1125,7 @@
     @Override
     public void engineLoad(LoadStoreParameter param) throws IOException,
             NoSuchAlgorithmException, CertificateException {
-        int namespace = KeyProperties.NAMESPACE_APPLICATION;
+        @KeyProperties.Namespace int namespace = KeyProperties.NAMESPACE_APPLICATION;
         if (param != null) {
             if (param instanceof AndroidKeyStoreLoadStoreParameter) {
                 namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 3708e15..34c66a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+
 import android.annotation.UiContext;
 import android.app.ResourcesManager;
 import android.content.Context;
@@ -34,6 +36,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -44,14 +48,14 @@
 
     private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName();
 
-    // Display area info. mapped by displayIds.
+    /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
     private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
-    // Display area leashes. mapped by displayIds.
+    /** Display area leashes, which is mapped by display IDs. */
     private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
 
     private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners =
             new SparseArray<>();
-
+    /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
     private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
 
     private final Context mContext;
@@ -173,8 +177,9 @@
         final Display display = mContext.getSystemService(DisplayManager.class)
                 .getDisplay(displayId);
         if (display == null) {
-            throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
-                    + "displayAreaInfo:" + displayAreaInfo);
+            ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed."
+                    + " Skip following steps", displayId);
+            return;
         }
         DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
         if (daContext == null) {
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index a5e2815..fdf0f59 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -61,6 +61,8 @@
     public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 4096;
     /** @hide */
     public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 8192;
+    /** @hide */
+    public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 16384;
 
     /** @hide */
     @IntDef(flag = true, prefix = {"TOP_HAL_CAPABILITY_"}, value = {TOP_HAL_CAPABILITY_SCHEDULING,
@@ -69,7 +71,8 @@
             TOP_HAL_CAPABILITY_MEASUREMENTS, TOP_HAL_CAPABILITY_NAV_MESSAGES,
             TOP_HAL_CAPABILITY_LOW_POWER_MODE, TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST,
             TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS, TOP_HAL_CAPABILITY_ANTENNA_INFO,
-            TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT})
+            TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT,
+            TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING})
 
     @Retention(RetentionPolicy.SOURCE)
     public @interface TopHalCapabilityFlags {}
@@ -351,6 +354,17 @@
     }
 
     /**
+     * Returns {@code true} if GNSS chipset will benefit from measurement corrections for driving
+     * use case if provided, {@code false} otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean hasMeasurementCorrectionsForDriving() {
+        return (mTopFlags & TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING) != 0;
+    }
+
+    /**
      * Returns {@code true} if GNSS chipset supports line-of-sight satellite identification
      * measurement corrections, {@code false} otherwise.
      *
@@ -550,6 +564,9 @@
         if (hasMeasurementCorrelationVectors()) {
             builder.append("MEASUREMENT_CORRELATION_VECTORS ");
         }
+        if (hasMeasurementCorrectionsForDriving()) {
+            builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING ");
+        }
         if (hasMeasurementCorrectionsLosSats()) {
             builder.append("LOS_SATS ");
         }
@@ -748,6 +765,18 @@
         }
 
         /**
+         * Sets measurement corrections for driving capability.
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setHasMeasurementCorrectionsForDriving(boolean capable) {
+            mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING,
+                    capable);
+            return this;
+        }
+
+        /**
          * Sets measurement corrections line-of-sight satellites capabilitity.
          *
          * @hide
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 9774e80..ac96e6f 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -88,7 +88,7 @@
         if (ints == null || ints.length == 0) {
             return "";
         }
-        return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
-                .collect(Collectors.joining());
+        return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt))
+                .collect(Collectors.joining(", "));
     }
 }
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 0d61399..3399377 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
 import android.media.MediaRecorder.Source;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioPolicy;
+import android.media.metrics.LogSessionId;
 import android.media.permission.Identity;
 import android.media.projection.MediaProjection;
 import android.os.Binder;
@@ -282,9 +283,9 @@
 
     /**
      * The log session id used for metrics.
-     * A null or empty string here means it is not set.
+     * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
      */
-    private String mLogSessionId;
+    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
 
     //---------------------------------------------------------
     // Constructor, Finalize
@@ -1963,24 +1964,34 @@
     }
 
     /**
-     * Sets a string handle to this AudioRecord for metrics collection.
+     * Sets a {@link LogSessionId} instance to this AudioRecord for metrics collection.
      *
-     * @param logSessionId a string which is used to identify this object
-     *        to the metrics service.  Proper generated Ids must be obtained
-     *        from the Java metrics service and should be considered opaque.
-     *        Use null to remove the logSessionId association.
+     * @param logSessionId a {@link LogSessionId} instance which is used to
+     *        identify this object to the metrics service. Proper generated
+     *        Ids must be obtained from the Java metrics service and should
+     *        be considered opaque. Use
+     *        {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+     *        logSessionId association.
      * @throws IllegalStateException if AudioRecord not initialized.
-     *
-     * @hide
      */
-    public void setLogSessionId(@Nullable String logSessionId) {
+    public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+        Objects.requireNonNull(logSessionId);
         if (mState == STATE_UNINITIALIZED) {
             throw new IllegalStateException("AudioRecord not initialized");
         }
-        native_setLogSessionId(logSessionId);
+        String stringId = logSessionId.getStringId();
+        native_setLogSessionId(stringId);
         mLogSessionId = logSessionId;
     }
 
+    /**
+     * Returns the {@link LogSessionId}.
+     */
+    @NonNull
+    public LogSessionId getLogSessionId() {
+        return mLogSessionId;
+    }
+
     //---------------------------------------------------------
     // Interface definitions
     //--------------------
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index bccefdf..7a2b022 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.media.metrics.LogSessionId;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -45,6 +46,7 @@
 import java.nio.ByteOrder;
 import java.nio.NioUtils;
 import java.util.LinkedList;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -567,9 +569,9 @@
 
     /**
      * The log session id used for metrics.
-     * A null or empty string here means it is not set.
+     * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
      */
-    private String mLogSessionId;
+    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
 
     //--------------------------------
     // Used exclusively by native code
@@ -4044,24 +4046,35 @@
     }
 
     /**
-     * Sets a string handle to this AudioTrack for metrics collection.
+     * Sets a {@link LogSessionId} instance to this AudioTrack for metrics collection.
      *
-     * @param logSessionId a string which is used to identify this object
-     *        to the metrics service.  Proper generated Ids must be obtained
-     *        from the Java metrics service and should be considered opaque.
-     *        Use null to remove the logSessionId association.
+     * @param logSessionId a {@link LogSessionId} instance which is used to
+     *        identify this object to the metrics service. Proper generated
+     *        Ids must be obtained from the Java metrics service and should
+     *        be considered opaque. Use
+     *        {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+     *        logSessionId association.
      * @throws IllegalStateException if AudioTrack not initialized.
      *
-     * @hide
      */
-    public void setLogSessionId(@Nullable String logSessionId) {
+    public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+        Objects.requireNonNull(logSessionId);
         if (mState == STATE_UNINITIALIZED) {
             throw new IllegalStateException("track not initialized");
         }
-        native_setLogSessionId(logSessionId);
+        String stringId = logSessionId.getStringId();
+        native_setLogSessionId(stringId);
         mLogSessionId = logSessionId;
     }
 
+    /**
+     * Returns the {@link LogSessionId}.
+     */
+    @NonNull
+    public LogSessionId getLogSessionId() {
+        return mLogSessionId;
+    }
+
     //---------------------------------------------------------
     // Inner classes
     //--------------------
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index a7e2b65..8db75d6 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,7 +25,6 @@
 import android.graphics.SurfaceTexture;
 import android.hardware.HardwareBuffer;
 import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.metrics.PlaybackComponent;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1539,7 +1538,7 @@
   </tbody>
  </table>
  */
-final public class MediaCodec implements PlaybackComponent {
+final public class MediaCodec {
 
     /**
      * Per buffer metadata includes an offset and size specifying
@@ -1697,22 +1696,6 @@
     private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
 
 
-    /**
-     * @hide
-     */
-    @Override
-    public void setPlaybackId(@NonNull String playbackId) {
-        // TODO: add a native method to pass the ID to the native code for logging.
-        mPlaybackId = playbackId;
-    }
-    /**
-     * @hide
-     */
-    @Override
-    public String getPlaybackId() {
-        return mPlaybackId;
-    }
-
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
 
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index ae64c02..10b99dc 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -27,7 +27,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.media.metrics.PlaybackComponent;
+import android.media.metrics.LogSessionId;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
@@ -50,6 +50,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
@@ -1379,7 +1380,7 @@
     public byte[] openSession(@SecurityLevel int level) throws
             NotProvisionedException, ResourceBusyException {
         byte[] sessionId = openSessionNative(level);
-        mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponentImpl(sessionId));
+        mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId));
         return sessionId;
     }
 
@@ -2929,8 +2930,8 @@
 
     /**
      * Obtain a {@link PlaybackComponent} associated with a DRM session.
-     * Call {@link PlaybackComponent#setPlaybackId(String)} on the returned object
-     * to associate a playback session with the DRM session.
+     * Call {@link PlaybackComponent#setLogSessionId(LogSessionId)} on
+     * the returned object to associate a playback session with the DRM session.
      *
      * @param sessionId a DRM session ID obtained from {@link #openSession()}
      * @return a {@link PlaybackComponent} associated with the session,
@@ -2945,28 +2946,37 @@
         return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId));
     }
 
-    private native void setPlaybackId(byte[] sessionId, String playbackId);
+    private native void setPlaybackId(byte[] sessionId, String logSessionId);
 
-    private final class PlaybackComponentImpl implements PlaybackComponent {
+    /** This class contains the Drm session ID and log session ID */
+    public final class PlaybackComponent {
         private final byte[] mSessionId;
-        private String mPlaybackId = "";
+        @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
 
-        public PlaybackComponentImpl(byte[] sessionId) {
+        /** @hide */
+        public PlaybackComponent(byte[] sessionId) {
             mSessionId = sessionId;
         }
 
-        @Override
-        public void setPlaybackId(@NonNull String playbackId) {
-            if (playbackId == null) {
+
+        /**
+         * Gets the {@link LogSessionId}.
+         */
+        public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+            Objects.requireNonNull(logSessionId);
+            if (logSessionId.getStringId() == null) {
                 throw new IllegalArgumentException("playbackId is null");
             }
-            MediaDrm.this.setPlaybackId(mSessionId, playbackId);
-            mPlaybackId = playbackId;
+            MediaDrm.this.setPlaybackId(mSessionId, logSessionId.getStringId());
+            mLogSessionId = logSessionId;
         }
 
-        @Override
-        @NonNull public String getPlaybackId() {
-            return mPlaybackId;
+
+        /**
+         * Returns the {@link LogSessionId}.
+         */
+        @NonNull public LogSessionId getLogSessionId() {
+            return mLogSessionId;
         }
     }
 
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 8f60330..283f1f1 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
+import android.media.metrics.LogSessionId;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.IHwBinder;
@@ -40,6 +41,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -73,7 +75,7 @@
  * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
  * when used with network-based content.
  */
-final public class MediaExtractor {
+public final class MediaExtractor {
     public MediaExtractor() {
         native_setup();
     }
@@ -768,6 +770,22 @@
     public native boolean hasCacheReachedEndOfStream();
 
     /**
+     * Sets the {@link LogSessionId} for MediaExtractor.
+     */
+    public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+        mLogSessionId = Objects.requireNonNull(logSessionId);
+        // TODO: implement native_setPlaybackId(playbackId);
+    }
+
+    /**
+     * Returns the {@link LogSessionId} for MediaExtractor.
+     */
+    @NonNull
+    public LogSessionId getLogSessionId() {
+        return mLogSessionId;
+    }
+
+    /**
      *  Return Metrics data about the current media container.
      *
      * @return a {@link PersistableBundle} containing the set of attributes and values
@@ -796,6 +814,7 @@
     }
 
     private MediaCas mMediaCas;
+    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
 
     private long mNativeContext;
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd08d8a..f960ff2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -29,6 +29,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.hardware.Camera;
+import android.media.metrics.LogSessionId;
 import android.media.permission.Identity;
 import android.os.Build;
 import android.os.Handler;
@@ -130,6 +131,8 @@
 
     private int mChannelCount;
 
+    @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
+
     /**
      * Default constructor.
      *
@@ -165,6 +168,27 @@
     }
 
     /**
+     * Sets the {@link LogSessionId} for MediaRecorder.
+     *
+     * @param id the global ID for monitoring the MediaRecorder performance
+     */
+    public void setLogSessionId(@NonNull LogSessionId id) {
+        Objects.requireNonNull(id);
+        mLogSessionId = id;
+        setParameter("log-session-id=" + id.getStringId());
+    }
+
+    /**
+     * Returns the {@link LogSessionId} for MediaRecorder.
+     *
+     * @return the global ID for monitoring the MediaRecorder performance
+     */
+    @NonNull
+    public LogSessionId getLogSessionId() {
+        return mLogSessionId;
+    }
+
+    /**
      * Sets a {@link android.hardware.Camera} to use for recording.
      *
      * <p>Use this function to switch quickly between preview and capture mode without a teardown of
diff --git a/media/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java
index f68ef4b..41f3093 100644
--- a/media/java/android/media/metrics/LogSessionId.java
+++ b/media/java/android/media/metrics/LogSessionId.java
@@ -17,20 +17,29 @@
 package android.media.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 
+import java.util.Objects;
+
 /**
  * An instances of this class represents the ID of a log session.
  */
 public final class LogSessionId {
-    private final String mSessionId;
+    @NonNull private final String mSessionId;
 
-    /* package */ LogSessionId(@NonNull String id) {
-        mSessionId = id;
-    }
+    /**
+     * A {@link LogSessionId} object which is used to clear any existing session ID.
+     */
+    @NonNull public static final LogSessionId LOG_SESSION_ID_NONE = new LogSessionId("");
 
     /** @hide */
     @TestApi
+    public LogSessionId(@NonNull String id) {
+        mSessionId = Objects.requireNonNull(id);
+    }
+
+    /** Returns the ID represented by a string. */
     @NonNull
     public String getStringId() {
         return mSessionId;
@@ -40,4 +49,17 @@
     public String toString() {
         return mSessionId;
     }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LogSessionId that = (LogSessionId) o;
+        return Objects.equals(mSessionId, that.mSessionId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSessionId);
+    }
 }
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 96bffee..3b0f577 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -39,6 +39,8 @@
     ISession createSession(String packageName, in ISessionCallback sessionCb, String tag,
             in Bundle sessionInfo, int userId);
     List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
+    MediaSession.Token getMediaKeyEventSession();
+    String getMediaKeyEventSessionPackageName();
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
             boolean needWakeLock);
     boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 78db750..269b70b 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -195,6 +195,44 @@
     }
 
     /**
+     * Gets the media key event session, which would receive a media key event unless specified.
+     * @return The media key event session, which would receive key events by default, unless
+     *          the caller has specified the target. Can be {@code null}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @Nullable
+    public MediaSession.Token getMediaKeyEventSession() {
+        try {
+            return mService.getMediaKeyEventSession();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to get media key event session", ex);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the package name of the media key event session.
+     * @return The package name of the media key event session or the last session's media button
+     *          receiver if the media key event session is {@code null}.
+     * @see #getMediaKeyEventSession()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @NonNull
+    public String getMediaKeyEventSessionPackageName() {
+        try {
+            String packageName = mService.getMediaKeyEventSessionPackageName();
+            return (packageName != null) ? packageName : "";
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to get media key event session", ex);
+        }
+        return "";
+    }
+
+    /**
      * Get active sessions for the given user.
      * <p>
      * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
@@ -856,7 +894,7 @@
     }
 
     /**
-     * Add a {@link OnMediaKeyEventDispatchedListener}.
+     * Add a {@link OnMediaKeyEventSessionChangedListener}.
      *
      * @param executor The executor on which the listener should be invoked
      * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 85513ca..6d7badb 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -287,6 +287,8 @@
     android_getaddrinfofornetwork; # introduced=23
     android_getprocnetwork; # introduced=31
     android_setprocnetwork; # introduced=23
+    android_getprocdns; # introduced=31
+    android_setprocdns; # introduced=31
     android_setsocknetwork; # introduced=23
     android_res_cancel; # introduced=29
     android_res_nquery; # introduced=29
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index cc8dd72..a6c1b50 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -16,6 +16,8 @@
     android_res_nsend; # llndk
     # These functions have been part of the NDK since API 31.
     android_getprocnetwork; # llndk
+    android_setprocdns; # llndk
+    android_getprocdns; # llndk
   local:
     *;
 };
diff --git a/native/android/net.c b/native/android/net.c
index d4b8888..e2f36a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -90,6 +90,38 @@
     return 0;
 }
 
+int android_setprocdns(net_handle_t network) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    int rval = setNetworkForResolv(netid);
+    if (rval < 0) {
+        errno = -rval;
+        rval = -1;
+    }
+    return rval;
+}
+
+int android_getprocdns(net_handle_t *network) {
+    if (network == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    unsigned netid;
+    int rval = getNetworkForDns(&netid);
+    if (rval < 0) {
+        errno = -rval;
+        return -1;
+    }
+
+    *network = gethandlefromnetid(netid);
+    return 0;
+}
+
 int android_getaddrinfofornetwork(net_handle_t network,
         const char *node, const char *service,
         const struct addrinfo *hints, struct addrinfo **res) {
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 8b99d21..f7c3965 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -11,6 +11,7 @@
     method @Nullable public android.net.ProxyInfo getGlobalProxy();
     method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
     method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
@@ -36,9 +37,11 @@
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
     method @Nullable public String getSubscriberId();
+    method public boolean isBypassableVpn();
   }
 
   public static final class NetworkAgentConfig.Builder {
+    method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
     method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
   }
 
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index b19efa3..9dcc391 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -270,6 +270,7 @@
 
   public final class NetworkCapabilities implements android.os.Parcelable {
     method @NonNull public int[] getAdministratorUids();
+    method @Nullable public static String getCapabilityCarrierName(int);
     method @Nullable public String getSsid();
     method @NonNull public int[] getTransportTypes();
     method public boolean isPrivateDnsBroken();
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c7c0bee..fd4d9db 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -123,11 +123,6 @@
     return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
 }
 
-static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
-{
-    return (jboolean) !queryUserAccess(uid, netId);
-}
-
 static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
 {
     if (env->GetArrayLength(addr) != len) {
@@ -267,7 +262,6 @@
     { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
     { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
-    { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
     { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
     { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
     { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index d490618..8c93676 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -3234,7 +3234,60 @@
         provider.setProviderId(NetworkProvider.ID_NONE);
     }
 
+    /**
+     * Register or update a network offer with ConnectivityService.
+     *
+     * ConnectivityService keeps track of offers made by the various providers and matches
+     * them to networking requests made by apps or the system. The provider supplies a score
+     * and the capabilities of the network it might be able to bring up ; these act as filters
+     * used by ConnectivityService to only send those requests that can be fulfilled by the
+     * provider.
+     *
+     * The provider is under no obligation to be able to bring up the network it offers at any
+     * given time. Instead, this mechanism is meant to limit requests received by providers
+     * to those they actually have a chance to fulfill, as providers don't have a way to compare
+     * the quality of the network satisfying a given request to their own offer.
+     *
+     * An offer can be updated by calling this again with the same callback object. This is
+     * similar to calling unofferNetwork and offerNetwork again, but will only update the
+     * provider with the changes caused by the changes in the offer.
+     *
+     * @param provider The provider making this offer.
+     * @param score The prospective score of the network.
+     * @param caps The prospective capabilities of the network.
+     * @param callback The callback to call when this offer is needed or unneeded.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
+    public void offerNetwork(@NonNull final int providerId,
+            @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
+            @NonNull final INetworkOfferCallback callback) {
+        try {
+            mService.offerNetwork(providerId,
+                    Objects.requireNonNull(score, "null score"),
+                    Objects.requireNonNull(caps, "null caps"),
+                    Objects.requireNonNull(callback, "null callback"));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
+    /**
+     * Withdraw a network offer made with {@link #offerNetwork}.
+     *
+     * @param callback The callback passed at registration time. This must be the same object
+     *                 that was passed to {@link #offerNetwork}
+     * @hide
+     */
+    public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
+        try {
+            mService.unofferNetwork(Objects.requireNonNull(callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
     /** @hide exposed via the NetworkProvider class. */
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
@@ -3704,8 +3757,9 @@
     private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
     private static CallbackHandler sCallbackHandler;
 
-    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
-            int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
+    private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need,
+            NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType,
+            CallbackHandler handler) {
         printStackTrace();
         checkCallbackNotNull(callback);
         if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
@@ -3730,8 +3784,8 @@
                             getAttributionTag());
                 } else {
                     request = mService.requestNetwork(
-                            need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType,
-                            callbackFlags, callingPackageName, getAttributionTag());
+                            asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
+                            legacyType, callbackFlags, callingPackageName, getAttributionTag());
                 }
                 if (request != null) {
                     sCallbacks.put(request, callback);
@@ -3746,6 +3800,12 @@
         return request;
     }
 
+    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+            int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
+        return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
+                legacyType, handler);
+    }
+
     /**
      * Helper function to request a network with a particular legacy type.
      *
@@ -4231,8 +4291,40 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
             @NonNull Handler handler) {
+        registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler);
+    }
+
+    /**
+     * Registers to receive notifications about changes in the default network for the specified
+     * UID. This may be a physical network or a virtual network, such as a VPN that applies to the
+     * UID. The callbacks will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+     * number of outstanding requests to 100 per app (identified by their UID), shared with
+     * all variants of this method, of {@link #requestNetwork} as well as
+     * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+     * Requesting a network with this method will count toward this limit. If this limit is
+     * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+     * make sure to unregister the callbacks with
+     * {@link #unregisterNetworkCallback(NetworkCallback)}.
+     *
+     * @param uid the UID for which to track default network changes.
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        UID's default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @throws RuntimeException if the app already has too many callbacks registered.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void registerDefaultNetworkCallbackAsUid(int uid,
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
         CallbackHandler cbHandler = new CallbackHandler(handler);
-        sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+        sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */,
                 TRACK_DEFAULT, TYPE_NONE, cbHandler);
     }
 
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 3300fa8..728f375 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -23,6 +23,7 @@
 import android.net.INetworkAgent;
 import android.net.IOnCompleteListener;
 import android.net.INetworkActivityListener;
+import android.net.INetworkOfferCallback;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.LinkProperties;
@@ -142,7 +143,7 @@
             in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config,
             in int factorySerialNumber);
 
-    NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType,
+    NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
             in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
             int callbackFlags, String callingPackageName, String callingAttributionTag);
 
@@ -221,4 +222,8 @@
             in IOnCompleteListener listener);
 
     int getRestrictBackgroundStatusByCaller();
+
+    void offerNetwork(int providerId, in NetworkScore score,
+            in NetworkCapabilities caps, in INetworkOfferCallback callback);
+    void unofferNetwork(in INetworkOfferCallback callback);
 }
diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
new file mode 100644
index 0000000..67d2d405
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkRequest;
+
+/**
+ * A callback registered with connectivity by network providers together with
+ * a NetworkOffer.
+ *
+ * When the offer is needed to satisfy some application or system component,
+ * connectivity will call onOfferNeeded on this callback. When this happens,
+ * the provider should try and bring up the network.
+ *
+ * When the offer is no longer needed, for example because the application has
+ * withdrawn the request or if the request is being satisfied by a network
+ * that this offer will never be able to beat, connectivity calls
+ * onOfferUnneeded. When this happens, the provider should stop trying to
+ * bring up the network, or tear it down if it has already been brought up.
+ *
+ * When NetworkProvider#offerNetwork is called, the provider can expect to
+ * immediately receive all requests that can be fulfilled by that offer and
+ * are not already satisfied by a better network. It is possible no such
+ * request is currently outstanding, because no requests have been made that
+ * can be satisfied by this offer, or because all such requests are already
+ * satisfied by a better network.
+ * onOfferNeeded can be called at any time after registration and until the
+ * offer is withdrawn with NetworkProvider#unofferNetwork is called. This
+ * typically happens when a new network request is filed by an application,
+ * or when the network satisfying a request disconnects and this offer now
+ * stands a chance to be the best network for it.
+ *
+ * @hide
+ */
+oneway interface INetworkOfferCallback {
+    /**
+     * Informs the registrant that the offer is needed to fulfill this request.
+     * @param networkRequest the request to satisfy
+     * @param providerId the ID of the provider currently satisfying
+     *          this request, or NetworkProvider.ID_NONE if none.
+     */
+    void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+
+    /**
+     * Informs the registrant that the offer is no longer needed to fulfill this request.
+     */
+    void onOfferUnneeded(in NetworkRequest networkRequest);
+}
diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
index 0741414..41fad63 100644
--- a/packages/Connectivity/framework/src/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -27,7 +27,6 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -526,11 +525,4 @@
     public String toString() {
         return Integer.toString(netId);
     }
-
-    /** @hide */
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        proto.write(NetworkProto.NET_ID, netId);
-        proto.end(token);
-    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index fb6fcc1..3f058d8 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -64,6 +64,16 @@
     }
 
     /**
+     * @return whether this VPN connection can be bypassed by the apps.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public boolean isBypassableVpn() {
+        return allowBypass;
+    }
+
+    /**
      * Set if the user desires to use this network even if it is unvalidated. This field has meaning
      * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
      * appropriate value based on previous user choice.
@@ -382,6 +392,19 @@
         }
 
         /**
+         * Sets whether the apps can bypass the VPN connection.
+         *
+         * @return this builder, to facilitate chaining.
+         * @hide
+         */
+        @NonNull
+        @SystemApi(client = MODULE_LIBRARIES)
+        public Builder setBypassableVpn(boolean allowBypass) {
+            mConfig.allowBypass = allowBypass;
+            return this;
+        }
+
+        /**
          * Returns the constructed {@link NetworkAgentConfig} object.
          */
         @NonNull
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 4924851..f50b018 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -35,7 +35,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Range;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
@@ -712,6 +711,23 @@
         return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
     }
 
+    /**
+     * Get the name of the given capability that carriers use.
+     * If the capability does not have a carrier-name, returns null.
+     *
+     * @param capability The capability to get the carrier-name of.
+     * @return The carrier-name of the capability, or null if it doesn't exist.
+     * @hide
+     */
+    @SystemApi
+    public static @Nullable String getCapabilityCarrierName(@NetCapability int capability) {
+        if (capability == NET_CAPABILITY_ENTERPRISE) {
+            return capabilityNameOf(capability);
+        } else {
+            return null;
+        }
+    }
+
     private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
         final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
         final long unwantedCaps =
@@ -2025,34 +2041,6 @@
         }
     }
 
-    /** @hide */
-    public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-
-        for (int transport : getTransportTypes()) {
-            proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport);
-        }
-
-        for (int capability : getCapabilities()) {
-            proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability);
-        }
-
-        proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps);
-        proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps);
-
-        if (mNetworkSpecifier != null) {
-            proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString());
-        }
-        if (mTransportInfo != null) {
-            // TODO b/120653863: write transport-specific info to proto?
-        }
-
-        proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength());
-        proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength);
-
-        proto.end(token);
-    }
-
     /**
      * @hide
      */
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index 14cb51c..d5b5c9b 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
@@ -28,6 +28,11 @@
 import android.os.Messenger;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+
 /**
  * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
  * to networks and makes them available to the core network stack by creating
@@ -78,7 +83,9 @@
      */
     @SystemApi
     public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) {
-        Handler handler = new Handler(looper) {
+        // TODO (b/174636568) : this class should be able to cache an instance of
+        // ConnectivityManager so it doesn't have to fetch it again every time.
+        final Handler handler = new Handler(looper) {
             @Override
             public void handleMessage(Message m) {
                 switch (m.what) {
@@ -159,4 +166,152 @@
     public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
         ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request);
     }
+
+    /** @hide */
+    // TODO : make @SystemApi when the impl is complete
+    public interface NetworkOfferCallback {
+        /** Called by the system when this offer is needed to satisfy some networking request. */
+        void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
+        /** Called by the system when this offer is no longer needed. */
+        void onOfferUnneeded(@NonNull NetworkRequest request);
+    }
+
+    private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
+        @NonNull public final NetworkOfferCallback callback;
+        @NonNull private final Executor mExecutor;
+
+        NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback,
+                @NonNull final Executor executor) {
+            this.callback = callback;
+            this.mExecutor = executor;
+        }
+
+        @Override
+        public void onOfferNeeded(final @NonNull NetworkRequest request,
+                final int providerId) {
+            mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+        }
+
+        @Override
+        public void onOfferUnneeded(final @NonNull NetworkRequest request) {
+            mExecutor.execute(() -> callback.onOfferUnneeded(request));
+        }
+    }
+
+    @GuardedBy("mProxies")
+    @NonNull private final ArrayList<NetworkOfferCallbackProxy> mProxies = new ArrayList<>();
+
+    // Returns the proxy associated with this callback, or null if none.
+    @Nullable
+    private NetworkOfferCallbackProxy findProxyForCallback(@NonNull final NetworkOfferCallback cb) {
+        synchronized (mProxies) {
+            for (final NetworkOfferCallbackProxy p : mProxies) {
+                if (p.callback == cb) return p;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Register or update an offer for network with the passed caps and score.
+     *
+     * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+     * connectivity stack what kind of network it may provide. The score and caps arguments act
+     * as filters that the connectivity stack uses to tell when the offer is necessary. When an
+     * offer might be advantageous over existing networks, the provider will receive a call to
+     * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
+     * should then try to bring up this network. When an offer is no longer needed, the stack
+     * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+     * provider should stop trying to bring up such a network, or disconnect it if it already has
+     * one.
+     *
+     * The stack determines what offers are needed according to what networks are currently
+     * available to the system, and what networking requests are made by applications. If an
+     * offer looks like it could be a better choice than any existing network for any particular
+     * request, that's when the stack decides the offer is needed. If the current networking
+     * requests are all satisfied by networks that this offer can't possibly be a better match
+     * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
+     * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
+     * is called.
+     *
+     * Note that the offers are non-binding to the providers, in particular because providers
+     * often don't know if they will be able to bring up such a network at any given time. For
+     * example, no wireless network may be in range when the offer is needed. This is fine and
+     * expected ; the provider should simply continue to try to bring up the network and do so
+     * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
+     * with the best network currently available, or if none, keep the apps informed that no
+     * network can currently satisfy this request. When/if the provider can bring up the network,
+     * the connectivity stack will match it against requests, and inform interested apps of the
+     * availability of this network. This may, in turn, render the offer of some other provider
+     * unneeded if all requests it used to satisfy are now better served by this network.
+     *
+     * A network can become unneeded for a reason like the above : whether the provider managed
+     * to bring up the offered network after it became needed or not, some other provider may
+     * bring up a better network than this one, making this offer unneeded. A network may also
+     * become unneeded if the application making the request withdrew it (for example, after it
+     * is done transferring data, or if the user canceled an operation).
+     *
+     * The capabilities and score act as filters as to what requests the provider will see.
+     * They are not promises, but for best performance, the providers should strive to put
+     * as much known information as possible in the offer. For capabilities in particular, it
+     * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
+     * have them at first. This applies to INTERNET, for example ; if a provider thinks the
+     * network it can bring up for this offer may offer Internet access it should include the
+     * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET.
+     *
+     * TODO : in the future, to avoid possible infinite loops, there should be constraints on
+     * what can be put in capabilities of networks brought up for an offer. If a provider might
+     * bring up a network with or without INTERNET, then it should file two offers : this will
+     * let it know precisely what networks are needed, so it can avoid bringing up networks that
+     * won't actually satisfy requests and remove the risk for bring-up-bring-down loops.
+     *
+     * @hide
+     */
+    // TODO : make @SystemApi when the impl is complete
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void offerNetwork(@NonNull final NetworkScore score,
+            @NonNull final NetworkCapabilities caps, @NonNull final Executor executor,
+            @NonNull final NetworkOfferCallback callback) {
+        // Can't offer a network with a provider that is not yet registered or already unregistered.
+        final int providerId = mProviderId;
+        if (providerId == ID_NONE) return;
+        NetworkOfferCallbackProxy proxy = null;
+        synchronized (mProxies) {
+            for (final NetworkOfferCallbackProxy existingProxy : mProxies) {
+                if (existingProxy.callback == callback) {
+                    proxy = existingProxy;
+                    break;
+                }
+            }
+            if (null == proxy) {
+                proxy = new NetworkOfferCallbackProxy(callback, executor);
+                mProxies.add(proxy);
+            }
+        }
+        mContext.getSystemService(ConnectivityManager.class)
+                .offerNetwork(providerId, score, caps, proxy);
+    }
+
+    /**
+     * Withdraw a network offer previously made to the networking stack.
+     *
+     * If a provider can no longer provide a network they offered, it should call this method.
+     * An example of usage could be if the hardware necessary to bring up the network was turned
+     * off in UI by the user. Note that because offers are never binding, the provider might
+     * alternatively decide not to withdraw this offer and simply refuse to bring up the network
+     * even when it's needed. However, withdrawing the request is slightly more resource-efficient
+     * because the networking stack won't have to compare this offer to exiting networks to see
+     * if it could beat any of them, and may be advantageous to the provider's implementation that
+     * can rely on no longer receiving callbacks for a network that they can't bring up anyways.
+     *
+     * @hide
+     */
+    // TODO : make @SystemApi when the impl is complete
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void unofferNetwork(final @NonNull NetworkOfferCallback callback) {
+        final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
+        if (null == proxy) return;
+        mProxies.remove(proxy);
+        mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy);
+    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 38691ef..5313f08 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -47,7 +47,6 @@
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Range;
-import android.util.proto.ProtoOutputStream;
 
 import java.util.Arrays;
 import java.util.List;
@@ -675,18 +674,6 @@
         }
     }
 
-    /** @hide */
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-
-        proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type));
-        proto.write(NetworkRequestProto.REQUEST_ID, requestId);
-        proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType);
-        networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES);
-
-        proto.end(token);
-    }
-
     public boolean equals(@Nullable Object obj) {
         if (obj instanceof NetworkRequest == false) return false;
         NetworkRequest that = (NetworkRequest)obj;
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 16ae55f..f524859 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -103,7 +103,10 @@
      * Determine if {@code uid} can access network designated by {@code netId}.
      * @return {@code true} if {@code uid} can access network, {@code false} otherwise.
      */
-    public native static boolean queryUserAccess(int uid, int netId);
+    public static boolean queryUserAccess(int uid, int netId) {
+        // TODO (b/183485986): remove this method
+        return false;
+    }
 
     /**
      * DNS resolver series jni method.
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6c66b5..5e69a4e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -17,8 +17,8 @@
 
     // TODO(b/149540986): revert this change.
     static_libs: [
-         // All other dependent components should be put in
-         // "SettingsLibDependenciesWithoutWifiTracker".
+        // All other dependent components should be put in
+        // "SettingsLibDependenciesWithoutWifiTracker".
         "WifiTrackerLib",
     ],
 
@@ -27,7 +27,10 @@
 
     resource_dirs: ["res"],
 
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     min_sdk_version: "21",
 
@@ -68,6 +71,7 @@
         "SettingsLibUsageProgressBarPreference",
         "SettingsLibCollapsingToolbarBaseActivity",
         "SettingsLibTwoTargetPreference",
+        "SettingsLibSettingsTransition",
     ],
 }
 
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
new file mode 100644
index 0000000..c12f6f7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -0,0 +1,22 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibSettingsTransition",
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "com.google.android.material_material",
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
new file mode 100644
index 0000000..11660a5f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.transition">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/SettingsTransition/res/values/dimens.xml b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
new file mode 100644
index 0000000..0630ca8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="settings_shared_axis_x_slide_distance">96dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
new file mode 100644
index 0000000..f99fda0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.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.settingslib.transition;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.Window;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.google.android.material.transition.platform.MaterialSharedAxis;
+import com.google.android.material.transition.platform.SlideDistanceProvider;
+
+/**
+ * A helper class to apply Settings Transition
+ */
+public class SettingsTransitionHelper {
+
+    private static final String TAG = "SettingsTransitionHelper";
+    private static final long DURATION = 450L;
+
+    private static MaterialSharedAxis createSettingsSharedAxis(Context context, boolean forward) {
+        final MaterialSharedAxis transition = new MaterialSharedAxis(MaterialSharedAxis.X, forward);
+        transition.excludeTarget(android.R.id.statusBarBackground, true);
+        transition.excludeTarget(android.R.id.navigationBarBackground, true);
+
+        final SlideDistanceProvider forwardDistanceProvider =
+                (SlideDistanceProvider) transition.getPrimaryAnimatorProvider();
+        final int distance = context.getResources().getDimensionPixelSize(
+                R.dimen.settings_shared_axis_x_slide_distance);
+        forwardDistanceProvider.setSlideDistance(distance);
+        transition.setDuration(DURATION);
+
+        final Interpolator interpolator =
+                AnimationUtils.loadInterpolator(context,
+                        android.R.interpolator.fast_out_extra_slow_in);
+        transition.setInterpolator(interpolator);
+
+        // TODO(b/177480673): Update fade through threshold once (cl/362065364) is released
+
+        return transition;
+    }
+
+    /**
+     * Apply the forward transition to the {@link Activity}, including Exit Transition and Enter
+     * Transition.
+     *
+     * The Exit Transition takes effect when leaving the page, while the Enter Transition is
+     * triggered when the page is launched/entering.
+     */
+    public static void applyForwardTransition(Activity activity) {
+        if (activity == null) {
+            Log.w(TAG, "applyForwardTransition: Invalid activity!");
+            return;
+        }
+        final Window window = activity.getWindow();
+        if (window == null) {
+            Log.w(TAG, "applyForwardTransition: Invalid window!");
+            return;
+        }
+        final MaterialSharedAxis forward = createSettingsSharedAxis(activity, true);
+        window.setExitTransition(forward);
+        window.setEnterTransition(forward);
+    }
+
+    /**
+     * Apply the backward transition to the {@link Activity}, including Return Transition and
+     * Reenter Transition.
+     *
+     * Return Transition will be used to move Views out of the scene when the Window is preparing
+     * to close. Reenter Transition will be used to move Views in to the scene when returning from a
+     * previously-started Activity.
+     */
+    public static void applyBackwardTransition(Activity activity) {
+        if (activity == null) {
+            Log.w(TAG, "applyBackwardTransition: Invalid activity!");
+            return;
+        }
+        final Window window = activity.getWindow();
+        if (window == null) {
+            Log.w(TAG, "applyBackwardTransition: Invalid window!");
+            return;
+        }
+        final MaterialSharedAxis backward = createSettingsSharedAxis(activity, false);
+        window.setReturnTransition(backward);
+        window.setReenterTransition(backward);
+    }
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index 9130662..17f257d 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,9 +72,9 @@
     private void init(Context context) {
         setLayoutResource(R.layout.preference_two_target);
         mSmallIconSize = context.getResources().getDimensionPixelSize(
-                R.dimen.two_target_pref_small_icon_size);
+                resourceId(context, "dimen", "two_target_pref_small_icon_size"));
         mMediumIconSize = context.getResources().getDimensionPixelSize(
-                R.dimen.two_target_pref_medium_icon_size);
+                resourceId(context, "dimen", "two_target_pref_medium_icon_size"));
         final int secondTargetResId = getSecondTargetResId();
         if (secondTargetResId != 0) {
             setWidgetLayoutResource(secondTargetResId);
@@ -116,4 +116,8 @@
     protected int getSecondTargetResId() {
         return 0;
     }
+
+    private int resourceId(Context context, String type, String name) {
+        return context.getResources().getIdentifier(name, type, context.getPackageName());
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d801f1b..7186ec5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1467,7 +1467,7 @@
     <string name="data_connection_5g_plus" translatable="false">5G+</string>
 
     <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
-    <string name="data_connection_carrier_wifi">CWF</string>
+    <string name="data_connection_carrier_wifi">W+</string>
 
     <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
     <string name="cell_data_off_content_description">Mobile data off</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 1474f18..f62ca32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -86,6 +86,15 @@
         }
     }
 
+    @Override
+    protected void onDeveloperOptionsSwitchEnabled() {
+        super.onDeveloperOptionsSwitchEnabled();
+        if (isAvailable()) {
+            mPreference.setDisabledByAdmin(
+                    checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+        }
+    }
+
     public void enablePreference(boolean enabled) {
         if (isAvailable()) {
             mPreference.setEnabled(enabled);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 068efac..3877b1e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -749,7 +749,8 @@
                  Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
                  Settings.Secure.SUPPRESS_DOZE,
                  Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
-                 Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+                 Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+                 Settings.Secure.TRANSFORM_ENABLED);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 90f77e7..cff8ad1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -39,6 +39,7 @@
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
@@ -240,6 +241,9 @@
     <!-- Permission needed to run keyguard manager tests in CTS -->
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
 
+    <!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests -->
+    <uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" />
+
     <!-- Permission needed to test wallpaper component -->
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
diff --git a/packages/SystemUI/res/drawable/people_space_messages_count_background.xml b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml
new file mode 100644
index 0000000..0fc112e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+    <solid android:color="#9ED582" />
+    <corners android:radius="@dimen/people_space_messages_count_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index e4e4cd8..db1d46d 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -98,8 +98,8 @@
             android:orientation="horizontal"
             android:paddingTop="4dp"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
+            android:layout_height="wrap_content"
+            android:clipToOutline="true">
             <TextView
                 android:id="@+id/name"
                 android:gravity="center_vertical"
@@ -112,7 +112,21 @@
                 android:ellipsize="end"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
-
+            <TextView
+                android:id="@+id/messages_count"
+                android:gravity="end"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:background="@drawable/people_space_messages_count_background"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                />
             <ImageView
                 android:id="@+id/predefined_icon"
                 android:gravity="end|center_vertical"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff4e8e0..2393b74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1353,6 +1353,7 @@
 
     <dimen name="people_space_widget_radius">28dp</dimen>
     <dimen name="people_space_image_radius">20dp</dimen>
+    <dimen name="people_space_messages_count_radius">12dp</dimen>
     <dimen name="people_space_widget_background_padding">6dp</dimen>
     <dimen name="required_width_for_medium">146dp</dimen>
     <dimen name="required_width_for_large">138dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 691d111..94bf86a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2865,6 +2865,8 @@
     <string name="empty_status">Let’s chat tonight!</string>
     <!-- Default text for missed call notifications on their Conversation widget [CHAR LIMIT=20] -->
     <string name="missed_call">Missed call</string>
+    <!-- Text when a Notification may have more messages than the number indicated [CHAR LIMIT=5] -->
+    <string name="messages_count_overflow_indicator"><xliff:g id="number" example="7">%d</xliff:g>+</string>
     <!-- Description text for adding a Conversation widget [CHAR LIMIT=100] -->
     <string name="people_tile_description">See recent messages, missed calls, and status updates</string>
     <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] -->
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 d52a251..997c527 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,60 @@
 
 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.pm.PackageManager;
-import android.content.res.Resources;
-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";
-
-    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() {
+public class CarrierTextController extends ViewController<CarrierText> {
+    private final CarrierTextManager mCarrierTextManager;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    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 =
-                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
-            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..bb1d972
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,721 @@
+/*
+ * 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 android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.wifi.WifiManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+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 com.android.systemui.telephony.TelephonyListenerManager;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+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 Executor mMainExecutor;
+    private final Executor mBgExecutor;
+    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 TelephonyListenerManager mTelephonyListenerManager;
+    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 ActiveDataSubscriptionIdListener mPhoneStateListener =
+            new ActiveDataSubscriptionIdListener() {
+        @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,
+            TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager,
+            WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor,
+            @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        mContext = context;
+        mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+
+        mShowAirplaneMode = showAirplaneMode;
+        mShowMissingSim = showMissingSim;
+
+        mWifiManager = wifiManager;
+        mTelephonyManager = telephonyManager;
+        mSeparator = separator;
+        mTelephonyListenerManager = telephonyListenerManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
+        mSimErrorState = new boolean[mSimSlotsNumber];
+        mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mBgExecutor.execute(() -> {
+            boolean supported = mContext.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+            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) {
+        if (callback != null) {
+            mCarrierTextCallback = callback;
+            if (mNetworkSupported.get()) {
+                // Keyguard update monitor expects callbacks from main thread
+                mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
+                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+                mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+            } else {
+                // Don't listen and clear out the text when the device isn't a phone.
+                mMainExecutor.execute(() -> callback.updateCarrierInfo(
+                        new CarrierTextCallbackInfo("", null, false, null)
+                ));
+            }
+        } else {
+            mCarrierTextCallback = null;
+            mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
+        }
+    }
+
+    /**
+     * 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) {
+        mBgExecutor.execute(() -> 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) {
+            mMainExecutor.execute(() -> 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 TelephonyManager mTelephonyManager;
+        private final TelephonyListenerManager mTelephonyListenerManager;
+        private final WakefulnessLifecycle mWakefulnessLifecycle;
+        private final Executor mMainExecutor;
+        private final Executor mBgExecutor;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private boolean mShowAirplaneMode;
+        private boolean mShowMissingSim;
+
+        @Inject
+        public Builder(Context context, @Main Resources resources,
+                @Nullable WifiManager wifiManager,
+                TelephonyManager telephonyManager,
+                TelephonyListenerManager telephonyListenerManager,
+                WakefulnessLifecycle wakefulnessLifecycle,
+                @Main Executor mainExecutor, @Background Executor bgExecutor,
+                KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            mContext = context;
+            mSeparator = resources.getString(
+                    com.android.internal.R.string.kg_text_message_separator);
+            mWifiManager = wifiManager;
+            mTelephonyManager = telephonyManager;
+            mTelephonyListenerManager = telephonyListenerManager;
+            mWakefulnessLifecycle = wakefulnessLifecycle;
+            mMainExecutor = mainExecutor;
+            mBgExecutor = bgExecutor;
+            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,
+                    mTelephonyManager, mTelephonyListenerManager,
+                    mWakefulnessLifecycle, mMainExecutor, mBgExecutor, 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 9f32c03..e41d5a3 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;
@@ -44,6 +44,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
     private final FalsingCollector mFalsingCollector;
+    private final EmergencyButtonController mEmergencyButtonController;
     private CountDownTimer mCountdownTimer;
     protected KeyguardMessageAreaController mMessageAreaController;
     private boolean mDismissing;
@@ -73,12 +74,14 @@
             LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker, FalsingCollector falsingCollector) {
-        super(view, securityMode, keyguardSecurityCallback);
+            LatencyTracker latencyTracker, FalsingCollector falsingCollector,
+            EmergencyButtonController emergencyButtonController) {
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
         mFalsingCollector = falsingCollector;
+        mEmergencyButtonController = emergencyButtonController;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = messageAreaControllerFactory.create(kma);
     }
@@ -87,6 +90,7 @@
 
     @Override
     public void onInit() {
+        super.onInit();
         mMessageAreaController.init();
     }
 
@@ -95,10 +99,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 05f33a9..3d42da2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -42,6 +42,7 @@
     private final SecurityMode mSecurityMode;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final EmergencyButton mEmergencyButton;
+    private final EmergencyButtonController mEmergencyButtonController;
     private boolean mPaused;
 
 
@@ -69,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
@@ -157,6 +165,7 @@
         private final Resources mResources;
         private final LiftToActivateListener mLiftToActivateListener;
         private final TelephonyManager mTelephonyManager;
+        private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
         private final FalsingCollector mFalsingCollector;
         private final boolean mIsNewLayoutEnabled;
 
@@ -168,6 +177,7 @@
                 InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
                 @Main Resources resources, LiftToActivateListener liftToActivateListener,
                 TelephonyManager telephonyManager, FalsingCollector falsingCollector,
+                EmergencyButtonController.Factory emergencyButtonControllerFactory,
                 FeatureFlags featureFlags) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
@@ -178,6 +188,7 @@
             mResources = resources;
             mLiftToActivateListener = liftToActivateListener;
             mTelephonyManager = telephonyManager;
+            mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
             mFalsingCollector = falsingCollector;
             mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled();
         }
@@ -185,33 +196,40 @@
         /** 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, mFalsingCollector,
-                        mMessageAreaControllerFactory);
+                        emergencyButtonController, mMessageAreaControllerFactory);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mInputMethodManager, mMainExecutor, mResources, mFalsingCollector);
+                        mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
+                        mFalsingCollector);
+
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mFalsingCollector, mIsNewLayoutEnabled);
+                        mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+                        mIsNewLayoutEnabled);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        mIsNewLayoutEnabled);
+                        emergencyButtonController, mIsNewLayoutEnabled);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        mIsNewLayoutEnabled);
+                        emergencyButtonController, 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 e45dd8b..933a919 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -112,11 +112,13 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             InputMethodManager inputMethodManager,
+            EmergencyButtonController emergencyButtonController,
             @Main DelayableExecutor mainExecutor,
             @Main Resources resources,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, falsingCollector);
+                messageAreaControllerFactory, latencyTracker, falsingCollector,
+                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 e16c01a..f0d1e02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -32,7 +32,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;
@@ -54,6 +54,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
     private final FalsingCollector mFalsingCollector;
+    private final EmergencyButtonController mEmergencyButtonController;
     private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
 
     private KeyguardMessageAreaController mMessageAreaController;
@@ -189,12 +190,14 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             LatencyTracker latencyTracker,
             FalsingCollector falsingCollector,
+            EmergencyButtonController emergencyButtonController,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
-        super(view, securityMode, keyguardSecurityCallback);
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
         mFalsingCollector = falsingCollector;
+        mEmergencyButtonController = emergencyButtonController;
         mMessageAreaControllerFactory = messageAreaControllerFactory;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = mMessageAreaControllerFactory.create(kma);
@@ -222,11 +225,7 @@
             }
             return false;
         });
-
-        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) {
@@ -242,10 +241,7 @@
         super.onViewDetached();
         mLockPatternView.setOnPatternListener(null);
         mLockPatternView.setOnTouchListener(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/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index b156f81..8de4e7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -57,9 +57,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, falsingCollector);
+                messageAreaControllerFactory, latencyTracker, falsingCollector,
+                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 49099fa..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,10 +34,11 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 631c248..69328cd 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 b2bf2e6..fddbb3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -79,10 +79,10 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            boolean isNewLayoutEnabled) {
+            EmergencyButtonController emergencyButtonController, 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);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 620db48..50bd0c7 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;
 
@@ -86,10 +85,10 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            boolean isNewLayoutEnabled) {
+            EmergencyButtonController emergencyButtonController, 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);
@@ -198,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 83c2d1e..96e69ad 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;
@@ -299,8 +297,23 @@
     void onDensityOrFontScaleChanged() {
         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
         mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_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) {
         pw.println("KeyguardSliceView:");
         pw.println("  mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE));
@@ -466,8 +479,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
@@ -479,24 +491,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 934e768..31ec003 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
@@ -267,10 +275,10 @@
             mKeyguardSliceViewController.updateLockScreenMode(mode);
             if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
                 mView.setCanShowOwnerInfo(false);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             } else {
                 mView.setCanShowOwnerInfo(true);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             }
             updateAodIcons();
         }
@@ -296,7 +304,7 @@
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
                 refreshTime();
                 mView.updateOwnerInfo();
-                mView.updateLogoutView();
+                mView.updateLogoutView(shouldShowLogout());
             }
         }
 
@@ -314,12 +322,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/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 69e6ed0..9abc1e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -22,7 +22,6 @@
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 
 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_DPM_LOCK_NOW;
@@ -76,11 +75,11 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.SparseArray;
@@ -107,6 +106,7 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.RingerModeTracker;
 
@@ -290,6 +290,7 @@
     private boolean mDeviceInteractive;
     private boolean mScreenOn;
     private SubscriptionManager mSubscriptionManager;
+    private final TelephonyListenerManager mTelephonyListenerManager;
     private List<SubscriptionInfo> mSubscriptionInfo;
     private TrustManager mTrustManager;
     private UserManager mUserManager;
@@ -358,7 +359,8 @@
             };
 
     @VisibleForTesting
-    public PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    public TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener =
+            new TelephonyCallback.ActiveDataSubscriptionIdListener() {
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
             mActiveMobileDataSubscription = subId;
@@ -1614,9 +1616,11 @@
             StatusBarStateController statusBarStateController,
             LockPatternUtils lockPatternUtils,
             AuthController authController,
+            TelephonyListenerManager telephonyListenerManager,
             FeatureFlags featureFlags) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
+        mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
         mBackgroundExecutor = backgroundExecutor;
@@ -1865,8 +1869,7 @@
         mTelephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         if (mTelephonyManager != null) {
-            mTelephonyManager.listen(mPhoneStateListener,
-                    LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+            mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
             // Set initial sim states values.
             for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
                 int state = mTelephonyManager.getSimState(slot);
@@ -3123,7 +3126,7 @@
         TelephonyManager telephony =
                 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         if (telephony != null) {
-            telephony.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         }
 
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
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/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/media/java/android/media/metrics/PlaybackComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
similarity index 62%
rename from media/java/android/media/metrics/PlaybackComponent.java
rename to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
index 1cadf3b..ba0642f 100644
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -14,22 +14,19 @@
  * limitations under the License.
  */
 
-package android.media.metrics;
+package com.android.keyguard.dagger;
 
-import android.annotation.NonNull;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
 
 /**
- * Interface for playback related components used by playback metrics.
+ * Scope annotation for singleton items within the StatusBarComponent.
  */
-public interface PlaybackComponent {
-
-    /**
-     * Sets the playback ID of the component.
-     */
-    void setPlaybackId(@NonNull String playbackId);
-
-    /**
-     * Gets playback ID.
-     */
-    @NonNull String getPlaybackId();
-}
+@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/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index cd53a34..ac99f73 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -349,6 +349,9 @@
     }
 
     private void setPercentTextAtCurrentLevel() {
+        if (mBatteryPercentView == null) {
+            return;
+        }
         mBatteryPercentView.setText(
                 NumberFormat.getPercentInstance().format(mLevel / 100f));
         setContentDescription(
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 06b486e..a686fc0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
@@ -350,6 +351,7 @@
     @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
     @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
     @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
+    @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
 
     @Inject
     public Dependency() {
@@ -545,6 +547,7 @@
         mProviders.put(StatusBar.class, mStatusBar::get);
         mProviders.put(ProtoTracer.class, mProtoTracer::get);
         mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
+        mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 4afa969..45a0ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -32,7 +32,9 @@
 import android.util.Log;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 /**
@@ -45,6 +47,11 @@
     private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
 
     private Dialog mNewSessionDialog;
+    private final UiEventLogger mUiEventLogger;
+
+    public GuestResumeSessionReceiver(UiEventLogger uiEventLogger) {
+        mUiEventLogger = uiEventLogger;
+    }
 
     /**
      * Register this receiver with the {@link BroadcastDispatcher}
@@ -83,7 +90,7 @@
             int notFirstLogin = Settings.System.getIntForUser(
                     cr, SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
             if (notFirstLogin != 0) {
-                mNewSessionDialog = new ResetSessionDialog(context, userId);
+                mNewSessionDialog = new ResetSessionDialog(context, mUiEventLogger, userId);
                 mNewSessionDialog.show();
             } else {
                 Settings.System.putIntForUser(
@@ -153,9 +160,10 @@
         private static final int BUTTON_WIPE = BUTTON_NEGATIVE;
         private static final int BUTTON_DONTWIPE = BUTTON_POSITIVE;
 
+        private final UiEventLogger mUiEventLogger;
         private final int mUserId;
 
-        public ResetSessionDialog(Context context, int userId) {
+        ResetSessionDialog(Context context, UiEventLogger uiEventLogger, int userId) {
             super(context);
 
             setTitle(context.getString(R.string.guest_wipe_session_title));
@@ -167,15 +175,18 @@
             setButton(BUTTON_DONTWIPE,
                     context.getString(R.string.guest_wipe_session_dontwipe), this);
 
+            mUiEventLogger = uiEventLogger;
             mUserId = userId;
         }
 
         @Override
         public void onClick(DialogInterface dialog, int which) {
             if (which == BUTTON_WIPE) {
+                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_WIPE);
                 wipeGuestSession(getContext(), mUserId);
                 dismiss();
             } else if (which == BUTTON_DONTWIPE) {
+                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE);
                 cancel();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index b4858f4..fd0c4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -43,16 +43,16 @@
     private final List<AccessibilityTarget> mTargets;
 
     @IntDef({
-            AccessibilityTargetAdapter.FIRST_ITEM,
-            AccessibilityTargetAdapter.REGULAR_ITEM,
-            AccessibilityTargetAdapter.LAST_ITEM
+            ItemType.FIRST_ITEM,
+            ItemType.REGULAR_ITEM,
+            ItemType.LAST_ITEM
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface ItemType {}
-
-    private static final int FIRST_ITEM = 0;
-    private static final int REGULAR_ITEM = 1;
-    private static final int LAST_ITEM = 2;
+    @interface ItemType {
+        int FIRST_ITEM = 0;
+        int REGULAR_ITEM = 1;
+        int LAST_ITEM = 2;
+    }
 
     public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
         mTargets = targets;
@@ -65,11 +65,11 @@
                 R.layout.accessibility_floating_menu_item, parent,
                 /* attachToRoot= */ false);
 
-        if (itemType == FIRST_ITEM) {
+        if (itemType == ItemType.FIRST_ITEM) {
             return new TopViewHolder(root);
         }
 
-        if (itemType == LAST_ITEM) {
+        if (itemType == ItemType.LAST_ITEM) {
             return new BottomViewHolder(root);
         }
 
@@ -87,14 +87,14 @@
     @Override
     public int getItemViewType(int position) {
         if (position == 0) {
-            return FIRST_ITEM;
+            return ItemType.FIRST_ITEM;
         }
 
         if (position == (getItemCount() - 1)) {
-            return LAST_ITEM;
+            return ItemType.LAST_ITEM;
         }
 
-        return REGULAR_ITEM;
+        return ItemType.REGULAR_ITEM;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
new file mode 100644
index 0000000..a1149fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricSourceType
+import android.view.View
+import android.view.ViewGroup
+import com.android.internal.annotations.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/***
+ * Controls the ripple effect that shows when authentication is successful.
+ * The ripple uses the accent color of the current theme.
+ */
+@SysUISingleton
+class AuthRippleController @Inject constructor(
+    commandRegistry: CommandRegistry,
+    configurationController: ConfigurationController,
+    private val context: Context,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor
+) {
+    @VisibleForTesting
+    var rippleView: AuthRippleView = AuthRippleView(context, attrs = null)
+
+    val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+        override fun onBiometricAuthenticated(
+            userId: Int,
+            biometricSourceType: BiometricSourceType?,
+            isStrongBiometric: Boolean
+        ) {
+            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                rippleView.startRipple()
+            }
+        }
+    }
+
+    init {
+        val configurationChangedListener = object : ConfigurationController.ConfigurationListener {
+            override fun onUiModeChanged() {
+                updateRippleColor()
+            }
+            override fun onThemeChanged() {
+                updateRippleColor()
+            }
+            override fun onOverlayChanged() {
+                updateRippleColor()
+            }
+        }
+        configurationController.addCallback(configurationChangedListener)
+
+        commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
+    }
+
+    fun setSensorLocation(x: Float, y: Float) {
+        rippleView.setSensorLocation(x, y)
+    }
+
+    fun setViewHost(viewHost: View) {
+        // Add the ripple view to its host layout
+        viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+            override fun onViewDetachedFromWindow(view: View?) {}
+
+            override fun onViewAttachedToWindow(view: View?) {
+                (viewHost as ViewGroup).addView(rippleView)
+                keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+                viewHost.removeOnAttachStateChangeListener(this)
+            }
+        })
+
+        updateRippleColor()
+    }
+
+    private fun updateRippleColor() {
+        rippleView.setColor(
+            Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor)
+    }
+
+    inner class AuthRippleCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            rippleView.startRipple()
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: adb shell cmd statusbar auth-ripple")
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
new file mode 100644
index 0000000..2e32133
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.biometrics
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PointF
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.statusbar.charging.RippleShader
+
+private const val RIPPLE_ANIMATION_DURATION: Long = 950
+private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
+
+/**
+ * Expanding ripple effect on the transition from biometric authentication success to showing
+ * launcher.
+ */
+class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+    private var rippleInProgress: Boolean = false
+    private val rippleShader = RippleShader()
+    private val defaultColor: Int = 0xffffffff.toInt()
+    private val ripplePaint = Paint()
+
+    init {
+        rippleShader.color = defaultColor
+        rippleShader.progress = 0f
+        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+        ripplePaint.shader = rippleShader
+        visibility = View.GONE
+    }
+
+    fun setSensorLocation(x: Float, y: Float) {
+        rippleShader.origin = PointF(x, y)
+        rippleShader.radius = maxOf(x, y, width - x, height - y).toFloat()
+    }
+
+    fun startRipple() {
+        if (rippleInProgress) {
+            return // Ignore if ripple effect is already playing
+        }
+        val animator = ValueAnimator.ofFloat(0f, 1f)
+        animator.duration = RIPPLE_ANIMATION_DURATION
+        animator.addUpdateListener { animator ->
+            val now = animator.currentPlayTime
+            val phase = now / 30000f
+            rippleShader.progress = animator.animatedValue as Float
+            rippleShader.noisePhase = phase
+            invalidate()
+        }
+        animator.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                rippleInProgress = false
+                visibility = View.GONE
+            }
+        })
+        animator.start()
+        visibility = View.VISIBLE
+        rippleInProgress = true
+    }
+
+    fun setColor(color: Int) {
+        rippleShader.color = color
+    }
+
+    override fun onDraw(canvas: Canvas?) {
+        // draw over the entire screen
+        canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 98b3fe4..078ec9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -87,6 +87,7 @@
     @NonNull private final StatusBarStateController mStatusBarStateController;
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
     @NonNull private final DumpManager mDumpManager;
+    @NonNull private final AuthRippleController mAuthRippleController;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -305,7 +306,8 @@
             @Main DelayableExecutor fgExecutor,
             @NonNull StatusBar statusBar,
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @NonNull DumpManager dumpManager) {
+            @NonNull DumpManager dumpManager,
+            @NonNull AuthRippleController authRippleController) {
         mContext = context;
         mInflater = inflater;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -317,6 +319,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardViewManager = statusBarKeyguardViewManager;
         mDumpManager = dumpManager;
+        mAuthRippleController = authRippleController;
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -343,6 +346,10 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mBroadcastReceiver, filter);
+
+        mAuthRippleController.setViewHost(mStatusBar.getNotificationShadeWindowView());
+        mAuthRippleController.setSensorLocation(getSensorLocation().centerX(),
+                getSensorLocation().centerY());
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 98a703f..521c495 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Log;
 import android.util.TypedValue;
 
 import java.util.ArrayList;
@@ -39,6 +40,9 @@
             "com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
     private static final float SCALE = 0.5f;
 
+    private static final String NEW_COORDS_OVERRIDE =
+            "com.android.systemui.biometrics.UdfpsNewCoords";
+
     // Enroll with two center touches before going to guided enrollment
     private static final int NUM_CENTER_TOUCHES = 2;
 
@@ -68,21 +72,42 @@
         // Number of pixels per mm
         float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
                 context.getResources().getDisplayMetrics());
-
-        mGuidedEnrollmentPoints.add(new PointF( 2.00f * px,  0.00f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-1.80f * px,  1.31f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 0.88f * px,  2.70f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-3.62f * px,  2.54f * px));
-        mGuidedEnrollmentPoints.add(new PointF(-1.49f * px,  5.57f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 2.29f * px,  4.92f * px));
-        mGuidedEnrollmentPoints.add(new PointF( 3.82f * px,  1.78f * px));
+        boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                NEW_COORDS_OVERRIDE, 0,
+                UserHandle.USER_CURRENT) != 0;
+        if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
+            Log.v(TAG, "Using new coordinates");
+            mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-0.15f * px,  1.02f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 0.29f * px,  0.00f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-2.48f * px,  1.23f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-1.69f * px,  3.29f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-0.37f * px,  4.31f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 1.07f * px,  3.96f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 2.17f * px,  2.35f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 2.58f * px,  0.00f * px));
+        } else {
+            Log.v(TAG, "Using old coordinates");
+            mGuidedEnrollmentPoints.add(new PointF( 2.00f * px,  0.00f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-1.80f * px,  1.31f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 0.88f * px,  2.70f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-3.62f * px,  2.54f * px));
+            mGuidedEnrollmentPoints.add(new PointF(-1.49f * px,  5.57f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 2.29f * px,  4.92f * px));
+            mGuidedEnrollmentPoints.add(new PointF( 3.82f * px,  1.78f * px));
+        }
     }
 
     boolean shouldShowProgressBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ed3d5ec..8e4e308 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;
@@ -150,4 +151,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 553e5a7..1862718 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -73,8 +73,8 @@
 import android.service.dreams.IDreamManager;
 import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
-import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.transition.AutoTransition;
 import android.transition.TransitionManager;
@@ -138,6 +138,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.leak.RotationUtils;
@@ -303,7 +304,8 @@
             AudioManager audioManager, IDreamManager iDreamManager,
             DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
-            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            ConnectivityManager connectivityManager,
+            TelephonyListenerManager telephonyListenerManager,
             ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
             ConfigurationController configurationController, ActivityStarter activityStarter,
             KeyguardStateController keyguardStateController, UserManager userManager,
@@ -361,7 +363,7 @@
                 context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
 
         // get notified of phone state changes
-        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        telephonyListenerManager.addServiceStateListener(mPhoneStateListener);
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
@@ -2049,7 +2051,8 @@
         }
     };
 
-    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    private final TelephonyCallback.ServiceStateListener mPhoneStateListener =
+            new TelephonyCallback.ServiceStateListener() {
         @Override
         public void onServiceStateChanged(ServiceState serviceState) {
             if (!mHasTelephony) return;
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 de2e7c47..a747edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
 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;
@@ -63,8 +64,11 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class,
-        KeyguardQsUserSwitchComponent.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/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 93ce5a8..5bc1280 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -274,13 +274,17 @@
             return tile;
         }
         boolean isMissedCall = Objects.equals(notification.category, CATEGORY_MISSED_CALL);
-        Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(notification);
+        List<Notification.MessagingStyle.Message> messages =
+                getMessagingStyleMessages(notification);
 
-        if (!isMissedCall && message == null) {
+        if (!isMissedCall && ArrayUtils.isEmpty(messages)) {
             if (DEBUG) Log.d(TAG, "Notification has no content");
             return tile;
         }
 
+        // messages are in chronological order from most recent to least.
+        Notification.MessagingStyle.Message message = messages != null ? messages.get(0) : null;
+        int messagesCount = messages != null ? messages.size() : 0;
         // If it's a missed call notification and it doesn't include content, use fallback value,
         // otherwise, use notification content.
         boolean hasMessageText = message != null && !TextUtils.isEmpty(message.getText());
@@ -294,12 +298,16 @@
                 .setNotificationCategory(notification.category)
                 .setNotificationContent(content)
                 .setNotificationDataUri(dataUri)
+                .setMessagesCount(messagesCount)
                 .build();
     }
 
-    /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */
+    /**
+     * Returns {@link Notification.MessagingStyle.Message}s from the Notification in chronological
+     * order from most recent to least.
+     */
     @VisibleForTesting
-    public static Notification.MessagingStyle.Message getLastMessagingStyleMessage(
+    public static List<Notification.MessagingStyle.Message> getMessagingStyleMessages(
             Notification notification) {
         if (notification == null) {
             return null;
@@ -312,7 +320,7 @@
                         Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
                 sortedMessages.sort(Collections.reverseOrder(
                         Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp)));
-                return sortedMessages.get(0);
+                return sortedMessages;
             }
         }
         return null;
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index ae81ab04..bc196bf 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -58,8 +58,10 @@
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 
+import java.text.NumberFormat;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.regex.Matcher;
@@ -82,6 +84,8 @@
     private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
     private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
 
+    private static final int MESSAGES_COUNT_OVERFLOW = 7;
+
     private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
     private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
     private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
@@ -97,6 +101,9 @@
     private int mHeight;
     private int mLayoutSize;
 
+    private Locale mLocale;
+    private NumberFormat mIntegerFormat;
+
     PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
             int appWidgetId, Bundle options) {
         mContext = context;
@@ -354,12 +361,35 @@
             views.setViewVisibility(R.id.image, View.GONE);
             views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
         }
+        if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) {
+            views.setViewVisibility(R.id.messages_count, View.VISIBLE);
+            views.setTextViewText(R.id.messages_count,
+                    getMessagesCountText(mTile.getMessagesCount()));
+        }
         // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
         //  subtract 1 from maxLines when present.
         views.setViewVisibility(R.id.subtext, View.GONE);
         return views;
     }
 
+    // Some messaging apps only include up to 7 messages in their notifications.
+    private String getMessagesCountText(int count) {
+        if (count >= MESSAGES_COUNT_OVERFLOW) {
+            return mContext.getResources().getString(
+                    R.string.messages_count_overflow_indicator, MESSAGES_COUNT_OVERFLOW);
+        }
+
+        // Cache the locale-appropriate NumberFormat.  Configuration locale is guaranteed
+        // non-null, so the first time this is called we will always get the appropriate
+        // NumberFormat, then never regenerate it unless the locale changes on the fly.
+        final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+        if (!curLocale.equals(mLocale)) {
+            mLocale = curLocale;
+            mIntegerFormat = NumberFormat.getIntegerInstance(curLocale);
+        }
+        return mIntegerFormat.format(count);
+    }
+
     private RemoteViews createStatusRemoteViews(ConversationStatus status) {
         RemoteViews views = getViewForContentLayout();
         CharSequence statusText = status.getDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 4ad685e..776e8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -328,6 +328,7 @@
                     .setNotificationKey(null)
                     .setNotificationContent(null)
                     .setNotificationDataUri(null)
+                    .setMessagesCount(0)
                     // Reset missed calls category.
                     .setNotificationCategory(null)
                     .build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 54e8a2b..cc5a771 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -118,7 +118,19 @@
     QS_USER_DETAIL_CLOSE(426),
 
     @UiEvent(doc = "User switcher QS detail panel more settings pressed")
-    QS_USER_MORE_SETTINGS(427);
+    QS_USER_MORE_SETTINGS(427),
+
+    @UiEvent(doc = "The user has added a guest in the detail panel")
+    QS_USER_GUEST_ADD(754),
+
+    @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user")
+    QS_USER_GUEST_WIPE(755),
+
+    @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user")
+    QS_USER_GUEST_CONTINUE(756),
+
+    @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel")
+    QS_USER_GUEST_REMOVE(757);
 
     override fun getId() = _id
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 994da9a..8aa2d09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs;
 
+import android.app.ActivityManager;
 import android.database.ContentObserver;
 import android.os.Handler;
 
@@ -24,27 +25,36 @@
 
 /** Helper for managing a secure setting. **/
 public abstract class SecureSetting extends ContentObserver implements Listenable {
-    private static final int DEFAULT = 0;
-
-    private SecureSettings mSecureSettings;
+    private final SecureSettings mSecureSettings;
     private final String mSettingName;
+    private final int mDefaultValue;
 
     private boolean mListening;
     private int mUserId;
-    private int mObservedValue = DEFAULT;
+    private int mObservedValue;
 
     protected abstract void handleValueChanged(int value, boolean observedChange);
 
     public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
             int userId) {
+        this(secureSettings, handler, settingName, userId, 0);
+    }
+
+    public SecureSetting(SecureSettings secureSetting, Handler handler, String settingName) {
+        this(secureSetting, handler, settingName, ActivityManager.getCurrentUser());
+    }
+
+    public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
+            int userId, int defaultValue) {
         super(handler);
         mSecureSettings = secureSettings;
         mSettingName = settingName;
+        mObservedValue = mDefaultValue = defaultValue;
         mUserId = userId;
     }
 
     public int getValue() {
-        return mSecureSettings.getIntForUser(mSettingName, DEFAULT, mUserId);
+        return mSecureSettings.getIntForUser(mSettingName, mDefaultValue, mUserId);
     }
 
     public void setValue(int value) {
@@ -61,7 +71,7 @@
                     mSecureSettings.getUriFor(mSettingName), false, this, mUserId);
         } else {
             mSecureSettings.unregisterContentObserver(this);
-            mObservedValue = DEFAULT;
+            mObservedValue = mDefaultValue;
         }
     }
 
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 0dc0b30..5afe1c8 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;
@@ -59,7 +59,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;
@@ -149,7 +149,7 @@
                 }
             };
 
-    private static class Callback implements CarrierTextController.CarrierTextCallback {
+    private static class Callback implements CarrierTextManager.CarrierTextCallback {
         private H mHandler;
 
         Callback(H handler) {
@@ -157,7 +157,7 @@
         }
 
         @Override
-        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
             mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
         }
     }
@@ -165,7 +165,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 {
@@ -174,7 +174,7 @@
         mActivityStarter = activityStarter;
         mBgHandler = bgHandler;
         mNetworkController = networkController;
-        mCarrierTextController = carrierTextControllerBuilder
+        mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
                 .build();
@@ -192,7 +192,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();
@@ -243,10 +242,10 @@
             if (mNetworkController.hasVoiceCallingFeature()) {
                 mNetworkController.addCallback(mSignalCallback);
             }
-            mCarrierTextController.setListening(mCallback);
+            mCarrierTextManager.setListening(mCallback);
         } else {
             mNetworkController.removeCallback(mSignalCallback);
-            mCarrierTextController.setListening(null);
+            mCarrierTextManager.setListening(null);
         }
     }
 
@@ -273,7 +272,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;
@@ -327,13 +326,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;
@@ -345,7 +344,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();
@@ -362,13 +361,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/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 01afacf..04d1996 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -267,8 +267,14 @@
 
     @Override
     protected void onPause() {
-        Log.d(TAG, "onPause finishing=" + isFinishing());
+        Log.d(TAG, "onPause");
         super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        Log.d(TAG, "onStop finishing=" + isFinishing());
+        super.onStop();
         if (isFinishing()) {
             if (mScrollCaptureResponse != null) {
                 mScrollCaptureResponse.close();
@@ -297,12 +303,6 @@
     }
 
     @Override
-    protected void onStop() {
-        Log.d(TAG, "onStop");
-        super.onStop();
-    }
-
-    @Override
     protected void onDestroy() {
         Log.d(TAG, "onDestroy");
         super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c1ae292..badffce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -25,7 +25,6 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
 import static com.android.systemui.screenshot.LogConfig.logTag;
@@ -39,7 +38,6 @@
 import android.app.ExitTransitionCoordinator;
 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
 import android.app.Notification;
-import android.app.WindowContext;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -56,7 +54,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -76,9 +73,9 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.window.WindowContext;
 
 import com.android.internal.app.ChooserActivity;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
@@ -86,7 +83,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.util.DeviceConfigProxy;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -193,7 +189,6 @@
     private final AccessibilityManager mAccessibilityManager;
     private final MediaActionSound mCameraSound;
     private final ScrollCaptureClient mScrollCaptureClient;
-    private final DeviceConfigProxy mConfigProxy;
     private final PhoneWindow mWindow;
     private final DisplayManager mDisplayManager;
 
@@ -237,7 +232,6 @@
             ScreenshotNotificationsController screenshotNotificationsController,
             ScrollCaptureClient scrollCaptureClient,
             UiEventLogger uiEventLogger,
-            DeviceConfigProxy configProxy,
             ImageExporter imageExporter,
             @Main Executor mainExecutor) {
         mScreenshotSmartActions = screenshotSmartActions;
@@ -254,7 +248,6 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-        mConfigProxy = configProxy;
 
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -525,22 +518,17 @@
         // The window is focusable by default
         setWindowFocusable(true);
 
-        if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) {
-            View decorView = mWindow.getDecorView();
-
-            // Wait until this window is attached to request because it is
-            // the reference used to locate the target window (below).
-            withWindowAttached(() -> {
-                mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
-                if (mLastScrollCaptureRequest != null) {
-                    mLastScrollCaptureRequest.cancel(true);
-                }
-                mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
-                mLastScrollCaptureRequest.addListener(() ->
-                        onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
-            });
-        }
+        // Wait until this window is attached to request because it is
+        // the reference used to locate the target window (below).
+        withWindowAttached(() -> {
+            mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
+            if (mLastScrollCaptureRequest != null) {
+                mLastScrollCaptureRequest.cancel(true);
+            }
+            mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
+            mLastScrollCaptureRequest.addListener(() ->
+                    onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
+        });
 
         attachWindow();
         mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index ead6d32..e27c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,7 +189,7 @@
         blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
         val zoomOut = blurUtils.ratioOfBlurRadius(blur)
         try {
-            if (root.isAttachedToWindow) {
+            if (root.isAttachedToWindow && root.windowToken != null) {
                 wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
             } else {
                 Log.i(TAG, "Won't set zoom. Window not attached $root")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 815cfb3..a3a4014 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -656,11 +656,6 @@
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
         boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
         boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
-        if (Notification.DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())) {
-            // When back-porting S rules, if an app targets P/Q/R then enforce the new S rule on
-            // that notification.  If it's before P though, we still want to enforce legacy rules.
-            beforeS = beforeP;
-        }
         int smallHeight;
 
         View expandedView = layout.getExpandedChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 3728388..609ca97c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -25,6 +25,7 @@
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 
 import com.android.systemui.R;
@@ -101,6 +102,28 @@
         }
     };
 
+    /**
+     * Get the relative start padding of a view relative to this view. This recursively walks up the
+     * hierarchy and does the corresponding measuring.
+     *
+     * @param view the view to get the padding for. The requested view has to be a child of this
+     *             notification.
+     * @return the start padding
+     */
+    public int getRelativeStartPadding(View view) {
+        boolean isRtl = isLayoutRtl();
+        int startPadding = 0;
+        while (view.getParent() instanceof ViewGroup) {
+            View parent = (View) view.getParent();
+            startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft();
+            view = parent;
+            if (view == this) {
+                return startPadding;
+            }
+        }
+        return startPadding;
+    }
+
     protected Path getClipPath(boolean ignoreTranslation) {
         int left;
         int top;
@@ -109,15 +132,17 @@
         int height;
         float topRoundness = mAlwaysRoundBothCorners
                 ? mOutlineRadius : getCurrentBackgroundRadiusTop();
+
         if (!mCustomOutline) {
-            int translation = mShouldTranslateContents && !ignoreTranslation
-                    ? (int) getTranslation() : 0;
-            int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
-            left = Math.max(translation, 0) - halfExtraWidth;
-            top = mClipTopAmount + mBackgroundTop;
-            right = getWidth() + halfExtraWidth + Math.min(translation, 0);
+            // Extend left/right clip bounds beyond the notification by the
+            // 1) space between the notification and edge of screen
+            // 2) corner radius (so we do not see any rounding as the notification goes off screen)
+            left = (int) (-getRelativeStartPadding(this) - mOutlineRadius);
+            right = (int) (((View) getParent()).getWidth() + mOutlineRadius);
+
             // If the top is rounded we want the bottom to be at most at the top roundness, in order
             // to avoid the shadow changing when scrolling up.
+            top = mClipTopAmount + mBackgroundTop;
             bottom = Math.max(mMinimumHeightForClipping,
                     Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness)));
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 73e0804..1086d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -69,6 +69,8 @@
     private float mContentTranslation;
     protected boolean mLastInSection;
     protected boolean mFirstInSection;
+    private float mOutlineRadius;
+    private float mParentWidth;
 
     public ExpandableView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -79,6 +81,7 @@
     private void initDimens() {
         mContentShift = getResources().getDimensionPixelSize(
                 R.dimen.shelf_transform_content_shift);
+        mOutlineRadius = getResources().getDimensionPixelSize(R.dimen.notification_corner_radius);
     }
 
     @Override
@@ -150,6 +153,9 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+        if (getParent() != null) {
+            mParentWidth = ((View) getParent()).getWidth();
+        }
         updateClipping();
     }
 
@@ -436,11 +442,15 @@
 
     protected void updateClipping() {
         if (mClipToActualHeight && shouldClipToActualHeight()) {
-            int top = getClipTopAmount();
-            int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+            final int top = getClipTopAmount();
+            final int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
                     - mClipBottomAmount, top), mMinimumHeightForClipping);
-            int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
-            mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
+            // Extend left/right clip bounds beyond the notification by the
+            // 1) space between the notification and edge of screen
+            // 2) corner radius (so we do not see any rounding as the notification goes off screen)
+            final int left = (int) (-getRelativeStartPadding(this) - mOutlineRadius);
+            final int right = (int) (mParentWidth + mOutlineRadius);
+            mClipRect.set(left, top, right, bottom);
             setClipBounds(mClipRect);
         } else {
             setClipBounds(null);
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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 415cfff..d3da0bce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -83,6 +83,7 @@
 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;
@@ -298,6 +299,7 @@
     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;
@@ -313,6 +315,7 @@
     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
+    private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -565,6 +568,7 @@
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+            KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
             QSDetailDisplayer qsDetailDisplayer,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
@@ -589,6 +593,7 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+        mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mFeatureFlags = featureFlags;
         mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
         mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
@@ -693,7 +698,9 @@
         }
 
         updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
-                userAvatarView, keyguardUserSwitcherView);
+                userAvatarView,
+                mKeyguardStatusBar,
+                keyguardUserSwitcherView);
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                 R.id.notification_stack_scroller);
@@ -773,13 +780,21 @@
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
-            UserAvatarView userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) {
+            UserAvatarView userAvatarView,
+            KeyguardStatusBarView keyguardStatusBarView,
+            KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
         mKeyguardStatusViewController.init();
 
+        KeyguardStatusBarViewComponent statusBarViewComponent =
+                mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
+        mKeyguarStatusBarViewController =
+                statusBarViewComponent.getKeyguardStatusBarViewController();
+        mKeyguarStatusBarViewController.init();
+
         // Re-associate the clock container with the keyguard clock switch.
         KeyguardClockSwitchController keyguardClockSwitchController =
                 statusViewComponent.getKeyguardClockSwitchController();
@@ -919,7 +934,8 @@
                         showKeyguardUserSwitcher /* enabled */);
 
         mBigClockContainer.removeAllViews();
-        updateViewControllers(keyguardStatusView, userAvatarView, keyguardUserSwitcherView);
+        updateViewControllers(
+                keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 8a86021..cfaeb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -22,7 +22,6 @@
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -44,11 +43,11 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellSignalStrength;
-import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
@@ -74,6 +73,7 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.telephony.TelephonyListenerManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -108,6 +108,7 @@
 
     private final Context mContext;
     private final TelephonyManager mPhone;
+    private final TelephonyListenerManager mTelephonyListenerManager;
     private final WifiManager mWifiManager;
     private final ConnectivityManager mConnectivityManager;
     private final SubscriptionManager mSubscriptionManager;
@@ -121,7 +122,7 @@
     private final boolean mProviderModel;
     private Config mConfig;
 
-    private PhoneStateListener mPhoneStateListener;
+    private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     // Subcontrollers.
@@ -201,12 +202,14 @@
             BroadcastDispatcher broadcastDispatcher,
             ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager,
+            TelephonyListenerManager telephonyListenerManager,
             @Nullable WifiManager wifiManager,
             NetworkScoreManager networkScoreManager,
             AccessPointControllerImpl accessPointController,
             DemoModeController demoModeController) {
         this(context, connectivityManager,
                 telephonyManager,
+                telephonyListenerManager,
                 wifiManager,
                 networkScoreManager,
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
@@ -222,7 +225,9 @@
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
-            TelephonyManager telephonyManager, WifiManager wifiManager,
+            TelephonyManager telephonyManager,
+            TelephonyListenerManager telephonyListenerManager,
+            WifiManager wifiManager,
             NetworkScoreManager networkScoreManager,
             SubscriptionManager subManager, Config config, Looper bgLooper,
             CallbackHandler callbackHandler,
@@ -233,6 +238,7 @@
             BroadcastDispatcher broadcastDispatcher,
             DemoModeController demoModeController) {
         mContext = context;
+        mTelephonyListenerManager = telephonyListenerManager;
         mConfig = config;
         mReceiverHandler = new Handler(bgLooper);
         mCallbackHandler = callbackHandler;
@@ -372,23 +378,20 @@
         // exclusively for status bar icons.
         mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler);
         // Register the listener on our bg looper
-        mPhoneStateListener = new PhoneStateListener(mReceiverHandler::post) {
-            @Override
-            public void onActiveDataSubscriptionIdChanged(int subId) {
-                // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
-                // 1) A and B are in the same subscription group e.g. CBRS data switch. And
-                // 2) A was validated before the switch.
-                // This is to provide smooth transition for UI without showing cross during data
-                // switch.
-                if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
-                    if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
-                    mForceCellularValidated = true;
-                    mReceiverHandler.removeCallbacks(mClearForceValidated);
-                    mReceiverHandler.postDelayed(mClearForceValidated, 2000);
-                }
-                mActiveMobileDataSubscription = subId;
-                doUpdateMobileControllers();
+        mPhoneStateListener = subId -> {
+            // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
+            // 1) A and B are in the same subscription group e.g. CBRS data switch. And
+            // 2) A was validated before the switch.
+            // This is to provide smooth transition for UI without showing cross during data
+            // switch.
+            if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
+                if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
+                mForceCellularValidated = true;
+                mReceiverHandler.removeCallbacks(mClearForceValidated);
+                mReceiverHandler.postDelayed(mClearForceValidated, 2000);
             }
+            mActiveMobileDataSubscription = subId;
+            doUpdateMobileControllers();
         };
 
         mDemoModeController.addCallback(this);
@@ -428,7 +431,7 @@
             mSubscriptionListener = new SubListener();
         }
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
-        mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
 
         // broadcasts
         IntentFilter filter = new IntentFilter();
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 d4029e64..0da441d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -42,8 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
+import android.telephony.TelephonyCallback;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -69,6 +68,7 @@
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.tiles.UserDetailView;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.CreateUserActivity;
 
 import java.io.FileDescriptor;
@@ -99,12 +99,12 @@
     protected final Context mContext;
     protected final UserManager mUserManager;
     private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
-    private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
-            = new GuestResumeSessionReceiver();
+    private final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
     private final KeyguardStateController mKeyguardStateController;
     protected final Handler mHandler;
     private final ActivityStarter mActivityStarter;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final TelephonyListenerManager mTelephonyListenerManager;
     private final IActivityTaskManager mActivityTaskManager;
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
@@ -127,11 +127,14 @@
     public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
             @Main Handler handler, ActivityStarter activityStarter,
             BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger,
+            TelephonyListenerManager telephonyListenerManager,
             IActivityTaskManager activityTaskManager) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
+        mTelephonyListenerManager = telephonyListenerManager;
         mActivityTaskManager = activityTaskManager;
         mUiEventLogger = uiEventLogger;
+        mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(mUiEventLogger);
         mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -388,6 +391,7 @@
                 // haven't reloaded the user list yet.
                 return;
             }
+            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
             id = guest.id;
         } else if (record.isAddUser) {
             showAddUserDialog();
@@ -458,18 +462,15 @@
     }
 
     private void listenForCallState() {
-        final TelephonyManager tele =
-            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if (tele != null) {
-            tele.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-        }
+        mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
     }
 
-    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    private final TelephonyCallback.CallStateListener mPhoneStateListener =
+            new TelephonyCallback.CallStateListener() {
         private int mCallState;
 
         @Override
-        public void onCallStateChanged(int state, String incomingNumber) {
+        public void onCallStateChanged(int state) {
             if (mCallState == state) return;
             if (DEBUG) Log.v(TAG, "Call state changed: " + state);
             mCallState = state;
@@ -891,6 +892,7 @@
             if (which == BUTTON_NEGATIVE) {
                 cancel();
             } else {
+                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
                 dismiss();
                 exitGuest(mGuestId, mTargetId);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
new file mode 100644
index 0000000..3bc2632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
@@ -0,0 +1,93 @@
+/*
+ * 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.telephony;
+
+import android.telephony.ServiceState;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+class TelephonyCallback extends android.telephony.TelephonyCallback
+        implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener {
+
+    private final List<ActiveDataSubscriptionIdListener> mActiveDataSubscriptionIdListeners =
+            new ArrayList<>();
+    private final List<CallStateListener> mCallStateListeners = new ArrayList<>();
+    private final List<ServiceStateListener> mServiceStateListeners = new ArrayList<>();
+
+    @Inject
+    TelephonyCallback() {
+    }
+
+    boolean hasAnyListeners() {
+        return !mActiveDataSubscriptionIdListeners.isEmpty()
+                || !mCallStateListeners.isEmpty()
+                || !mServiceStateListeners.isEmpty();
+    }
+
+    @Override
+    public void onActiveDataSubscriptionIdChanged(int subId) {
+        mActiveDataSubscriptionIdListeners.forEach(listener -> {
+            listener.onActiveDataSubscriptionIdChanged(subId);
+        });
+    }
+
+    void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+        mActiveDataSubscriptionIdListeners.add(listener);
+    }
+
+    void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+        mActiveDataSubscriptionIdListeners.remove(listener);
+    }
+
+    @Override
+    public void onCallStateChanged(int state) {
+        mCallStateListeners.forEach(listener -> {
+            listener.onCallStateChanged(state);
+        });
+    }
+
+    void addCallStateListener(CallStateListener listener) {
+        mCallStateListeners.add(listener);
+    }
+
+    void removeCallStateListener(CallStateListener listener) {
+        mCallStateListeners.remove(listener);
+    }
+
+    @Override
+    public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+        mServiceStateListeners.forEach(listener -> {
+            listener.onServiceStateChanged(serviceState);
+        });
+    }
+
+    void addServiceStateListener(ServiceStateListener listener) {
+        mServiceStateListeners.add(listener);
+    }
+
+    void removeServiceStateListener(ServiceStateListener listener) {
+        mServiceStateListeners.remove(listener);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
new file mode 100644
index 0000000..4e1acca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.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.systemui.telephony;
+
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+import android.telephony.TelephonyManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Wrapper around {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ *
+ * The TelephonyManager complains if too many places in code register a listener. This class
+ * encapsulates SystemUI's usage of this function, reducing it down to a single listener.
+ *
+ * See also
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, android.telephony.TelephonyCallback)}
+ */
+@SysUISingleton
+public class TelephonyListenerManager {
+    private final TelephonyManager mTelephonyManager;
+    private final Executor mExecutor;
+    private final TelephonyCallback mTelephonyCallback;
+
+    private boolean mListening = false;
+
+    @Inject
+    public TelephonyListenerManager(TelephonyManager telephonyManager, @Main Executor executor,
+            TelephonyCallback telephonyCallback) {
+        mTelephonyManager = telephonyManager;
+        mExecutor = executor;
+        mTelephonyCallback = telephonyCallback;
+    }
+
+    /** */
+    public void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+        mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+        updateListening();
+    }
+
+    /** */
+    public void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+        mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+        updateListening();
+    }
+
+    /** */
+    public void addCallStateListener(CallStateListener listener) {
+        mTelephonyCallback.addCallStateListener(listener);
+        updateListening();
+    }
+
+    /** */
+    public void removeCallStateListener(CallStateListener listener) {
+        mTelephonyCallback.removeCallStateListener(listener);
+        updateListening();
+    }
+
+    /** */
+    public void addServiceStateListener(ServiceStateListener listener) {
+        mTelephonyCallback.addServiceStateListener(listener);
+        updateListening();
+    }
+
+    /** */
+    public void removeServiceStateListener(ServiceStateListener listener) {
+        mTelephonyCallback.removeServiceStateListener(listener);
+        updateListening();
+    }
+
+
+    private void updateListening() {
+        if (!mListening && mTelephonyCallback.hasAnyListeners()) {
+            mListening = true;
+            mTelephonyManager.registerTelephonyCallback(mExecutor, mTelephonyCallback);
+        } else if (mListening && !mTelephonyCallback.hasAnyListeners()) {
+            mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
+            mListening = false;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 6f2c0af..c2c7dde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -21,12 +21,13 @@
 import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,10 +41,6 @@
 import android.content.pm.PackageManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
 import android.provider.Settings;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -51,13 +48,14 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-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;
+import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,8 +71,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 +92,9 @@
     @Mock
     private WifiManager mWifiManager;
     @Mock
-    private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
@@ -103,24 +102,25 @@
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
+    private TelephonyListenerManager mTelephonyListenerManager;
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+    @Mock
     private SubscriptionManager mSubscriptionManager;
-    private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
+    private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
 
-    private CarrierTextController mCarrierTextController;
-    private TestableLooper mTestableLooper;
+    private CarrierTextManager mCarrierTextManager;
 
     private Void checkMainThread(InvocationOnMock inv) {
-        Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper();
-        if (!mainLooper.isCurrentThread()) {
-            fail("This call should be done from the main thread");
-        }
+        assertThat(mMainExecutor.isExecuting()).isTrue();
+        assertThat(mBgExecutor.isExecuting()).isFalse();
         return null;
     }
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mTestableLooper = TestableLooper.get(this);
 
         mContext.addMockSystemService(WifiManager.class, mWifiManager);
         mContext.addMockSystemService(PackageManager.class, mPackageManager);
@@ -132,9 +132,6 @@
         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)
@@ -142,35 +139,30 @@
         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,
+                mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
+                mBgExecutor, 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);
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.setListening(mCarrierTextCallback);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
     }
 
     @Test
     public void testKeyguardUpdateMonitorCalledInMainThread() throws Exception {
-        // This test will run on the main looper (which is not the same as the looper set as MAIN
-        // for CarrierTextCallback. This will fail if calls to mKeyguardUpdateMonitor are not done
-        // through the looper set in the set up
-        HandlerThread thread = new HandlerThread("testThread",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        TestableLooper testableLooper = new TestableLooper(thread.getLooper());
-        Handler h = new Handler(testableLooper.getLooper());
-        h.post(() -> {
-            mCarrierTextController.setListening(null);
-            mCarrierTextController.setListening(mCarrierTextCallback);
-        });
-        testableLooper.processAllMessages();
-        mTestableLooper.processAllMessages();
-        thread.quitSafely();
+        mCarrierTextManager.setListening(null);
+        mCarrierTextManager.setListening(mCarrierTextCallback);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
     }
 
     @Test
@@ -183,13 +175,13 @@
         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();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
     }
@@ -205,14 +197,14 @@
                 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();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText);
         // There's only one subscription in the list
@@ -223,8 +215,8 @@
         reset(mCarrierTextCallback);
         when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
         // Update carrier text. It should ignore error state of subId 3 in inactive slotId.
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals("TEST_CARRIER", captor.getValue().carrierText);
     }
@@ -237,9 +229,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 +249,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();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(
-                any(CarrierTextController.CarrierTextCallbackInfo.class));
+                any(CarrierTextManager.CarrierTextCallbackInfo.class));
     }
 
     @Test
     public void testCallback() {
         reset(mCarrierTextCallback);
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
-        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,11 +274,11 @@
     public void testNullingCallback() {
         reset(mCarrierTextCallback);
 
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
-        mCarrierTextController.setListening(null);
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+        mCarrierTextManager.setListening(null);
 
         // This shouldn't produce NPE
-        mTestableLooper.processAllMessages();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(any());
     }
 
@@ -301,15 +293,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         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 +318,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         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,17 +338,17 @@
         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();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
@@ -380,12 +372,12 @@
         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();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertFalse("No SIM should be available", captor.getValue().anySimReady);
@@ -407,15 +399,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();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         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,17 +420,17 @@
         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();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
@@ -458,12 +450,12 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertEquals(TEST_CARRIER,
@@ -483,12 +475,12 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertEquals(TEST_CARRIER,
@@ -509,12 +501,12 @@
         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();
-        mTestableLooper.processAllMessages();
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
         assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 0cf343c..90f7fda 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -72,6 +72,8 @@
     @Mock
     private LatencyTracker mLatencyTracker;
     private final FalsingCollector mFalsingCollector = new FalsingCollectorFake();
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
 
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
 
@@ -87,7 +89,8 @@
                 .thenReturn(mKeyguardMessageArea);
         mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector) {
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
+                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 fc93ded..bb71bed8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -52,6 +52,8 @@
     private lateinit var mLatencyTracker: LatencyTracker
     private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
     @Mock
+    private lateinit var mEmergencyButtonController: EmergencyButtonController
+    @Mock
     private lateinit
     var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
     @Mock
@@ -75,7 +77,8 @@
                 .thenReturn(mKeyguardMessageAreaController)
         mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
         mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mLatencyTracker, mFalsingCollector, mKeyguardMessageAreaControllerFactory)
+                mLatencyTracker, mFalsingCollector, 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 33a0dcd..9597cab 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -68,6 +68,8 @@
     private LatencyTracker mLatencyTracker;
     @Mock
     private LiftToActivateListener mLiftToactivateListener;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
     private SingleTapClassifier mSingleTapClassifier;
@@ -97,7 +99,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/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 096ce0f..084c0b4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -94,6 +94,8 @@
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private ConfigurationController mConfigurationController;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
     private KeyguardPasswordViewController mKeyguardPasswordViewController;
@@ -112,11 +114,11 @@
         mKeyguardPasswordViewController = new KeyguardPasswordViewController(
                 (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
                 SecurityMode.Password, mLockPatternUtils, null,
-                mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
-                null);
+                mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
+                null, mock(Resources.class), null);
 
         mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
-                mView,  mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+                mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
                 mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
                 mConfigurationController)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 3b7f4b8..9296d3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -59,6 +59,10 @@
     @Mock
     private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
     @Mock
+    private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
+    @Mock
     private KeyguardInputViewController mKeyguardInputViewController;
     @Mock
     private KeyguardInputView mInputView;
@@ -76,9 +80,12 @@
                 any(KeyguardSecurityCallback.class)))
                 .thenReturn(mKeyguardInputViewController);
         when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
+                .thenReturn(mEmergencyButtonController);
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
-                mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+                mLayoutInflater, mKeyguardSecurityViewControllerFactory,
+                mEmergencyButtonControllerFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 52e2016..160dae5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -88,6 +88,7 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.RingerModeTracker;
 
 import org.junit.After;
@@ -163,6 +164,8 @@
     @Mock
     private AuthController mAuthController;
     @Mock
+    private TelephonyListenerManager mTelephonyListenerManager;
+    @Mock
     private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@@ -883,7 +886,7 @@
                     mBroadcastDispatcher, mDumpManager,
                     mRingerModeTracker, mBackgroundExecutor,
                     mStatusBarStateController, mLockPatternUtils,
-                    mAuthController, mFeatureFlags);
+                    mAuthController, mTelephonyListenerManager, mFeatureFlags);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
new file mode 100644
index 0000000..02ba304
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.biometrics
+
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.policy.ConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AuthRippleControllerTest : SysuiTestCase() {
+    private lateinit var controller: AuthRippleController
+    @Mock private lateinit var commandRegistry: CommandRegistry
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var rippleView: AuthRippleView
+    @Mock private lateinit var viewHost: ViewGroup
+
+    @Before
+
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        controller = AuthRippleController(
+            commandRegistry, configurationController, context, keyguardUpdateMonitor)
+        controller.rippleView = rippleView // Replace the real ripple view with a mock instance
+        controller.setViewHost(viewHost)
+    }
+
+    @Test
+    fun testAddRippleView() {
+        val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+        verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture())
+
+        // Fake attach to window
+        listenerCaptor.value.onViewAttachedToWindow(viewHost)
+        verify(viewHost).addView(rippleView)
+    }
+
+    @Test
+    fun testTriggerRipple() {
+        // Fake attach to window
+        val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+        verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture())
+        listenerCaptor.value.onViewAttachedToWindow(viewHost)
+
+        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+        captor.value.onBiometricAuthenticated(
+            0 /* userId */,
+            BiometricSourceType.FACE /* type */,
+            false /* isStrongBiometric */)
+        verify(rippleView, never()).startRipple()
+
+        captor.value.onBiometricAuthenticated(
+            0 /* userId */,
+            BiometricSourceType.FINGERPRINT /* type */,
+            false /* isStrongBiometric */)
+        verify(rippleView).startRipple()
+    }
+
+    @Test
+    fun testUpdateRippleColor() {
+        val captor = ArgumentCaptor
+            .forClass(ConfigurationController.ConfigurationListener::class.java)
+        verify(configurationController).addCallback(captor.capture())
+
+        reset(rippleView)
+        captor.value.onThemeChanged()
+        verify(rippleView).setColor(ArgumentMatchers.anyInt())
+
+        reset(rippleView)
+        captor.value.onUiModeChanged()
+        verify(rippleView).setColor(ArgumentMatchers.anyInt())
+    }
+
+    @Test
+    fun testForwardsSensorLocation() {
+        controller.setSensorLocation(5f, 5f)
+        verify(rippleView).setSensorLocation(5f, 5f)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 3f1a927..d3694dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -98,6 +98,8 @@
     @Mock
     private DumpManager mDumpManager;
     @Mock
+    private AuthRippleController mAuthRippleController;
+    @Mock
     private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
 
     private FakeExecutor mFgExecutor;
@@ -148,7 +150,8 @@
                 mFgExecutor,
                 mStatusBar,
                 mStatusBarKeyguardViewManager,
-                mDumpManager);
+                mDumpManager,
+                mAuthRippleController);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
 
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 eedf099..8add930 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -43,7 +43,6 @@
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.service.dreams.IDreamManager;
-import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.IWindowManager;
@@ -75,6 +74,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -106,7 +106,7 @@
     @Mock private LockPatternUtils mLockPatternUtils;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private ConnectivityManager mConnectivityManager;
-    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private TelephonyListenerManager mTelephonyListenerManager;
     @Mock private ContentResolver mContentResolver;
     @Mock private Resources mResources;
     @Mock private ConfigurationController mConfigurationController;
@@ -167,7 +167,7 @@
                 mLockPatternUtils,
                 mBroadcastDispatcher,
                 mConnectivityManager,
-                mTelephonyManager,
+                mTelephonyListenerManager,
                 mContentResolver,
                 null,
                 mResources,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 1c7a84a..1f4dffa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -65,6 +65,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.util.ArrayUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.people.widget.PeopleTileKey;
@@ -119,6 +120,7 @@
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
+                    .setMessagesCount(1)
                     .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
@@ -318,7 +320,7 @@
     }
 
     @Test
-    public void testGetLastMessagingStyleMessageNoMessage() {
+    public void testGetMessagingStyleMessagesNoMessage() {
         Notification notification = new Notification.Builder(mContext, "test")
                 .setContentTitle("TEST_TITLE")
                 .setContentText("TEST_TEXT")
@@ -328,22 +330,23 @@
                 .setNotification(notification)
                 .build();
 
-        Notification.MessagingStyle.Message lastMessage =
-                PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification());
+        List<Notification.MessagingStyle.Message> messages =
+                PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification());
 
-        assertThat(lastMessage).isNull();
+        assertThat(ArrayUtils.isEmpty(messages)).isTrue();
     }
 
     @Test
-    public void testGetLastMessagingStyleMessage() {
+    public void testGetMessagingStyleMessages() {
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(mNotification1)
                 .build();
 
-        Notification.MessagingStyle.Message lastMessage =
-                PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification());
+        List<Notification.MessagingStyle.Message> messages =
+                PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification());
 
-        assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+        assertThat(messages.size()).isEqualTo(3);
+        assertThat(messages.get(0).getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 39bf060..c2e0d6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -467,6 +467,9 @@
         assertEquals(View.VISIBLE,
                 smallResult.findViewById(R.id.person_icon).getVisibility());
 
+        // Has a single message, no count shown.
+        assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
@@ -492,6 +495,36 @@
     }
 
     @Test
+    public void testCreateRemoteViewsWithNotificationTemplateTwoMessages() {
+        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+                .setNotificationDataUri(null)
+                .setStatuses(Arrays.asList(GAME_STATUS,
+                        NEW_STORY_WITH_AVAILABILITY))
+                .setMessagesCount(2).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        TextView subtext = (TextView) result.findViewById(R.id.subtext);
+        assertEquals(View.GONE, subtext.getVisibility());
+        // Has availability.
+        View availability = result.findViewById(R.id.availability);
+        assertEquals(View.VISIBLE, availability.getVisibility());
+        // Has person icon.
+        View personIcon = result.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+
+        // Has 2 messages, show count.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+    }
+
+
+    @Test
     public void testGetBackgroundTextFromMessageNoPunctuation() {
         String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test");
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
index 418fa61..6af8402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
@@ -41,6 +41,7 @@
         private const val TEST_SETTING = "setting"
         private const val USER = 0
         private const val OTHER_USER = 1
+        private const val DEFAULT_VALUE = 1
         private val FAIL_CALLBACK: Callback = { _, _ -> fail("Callback should not be called") }
     }
 
@@ -59,7 +60,8 @@
                 secureSettings,
                 Handler(testableLooper.looper),
                 TEST_SETTING,
-                USER
+                USER,
+                DEFAULT_VALUE
         ) {
             override fun handleValueChanged(value: Int, observedChange: Boolean) {
                 callback(value, observedChange)
@@ -150,4 +152,14 @@
 
         assertThat(changed).isTrue()
     }
+
+    @Test
+    fun testDefaultValue() {
+        // Check default value before listening
+        assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+
+        // Check default value if setting is not set
+        setting.isListening = true
+        assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+    }
 }
\ No newline at end of file
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 5a1bd5f..59a5f03 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.statusbar.policy.NetworkController.MobileDataIndicators;
@@ -55,7 +55,7 @@
 
     private QSCarrierGroupController mQSCarrierGroupController;
     private NetworkController.SignalCallback mSignalCallback;
-    private CarrierTextController.CarrierTextCallback mCallback;
+    private CarrierTextManager.CarrierTextCallback mCallback;
     @Mock
     private QSCarrierGroup mQSCarrierGroup;
     @Mock
@@ -63,9 +63,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
@@ -84,11 +84,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));
@@ -114,8 +114,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,
@@ -123,8 +123,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,
@@ -132,8 +132,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,
@@ -141,8 +141,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,
@@ -160,8 +160,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,
@@ -169,8 +169,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,
@@ -178,8 +178,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,
@@ -187,8 +187,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,
@@ -204,8 +204,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,
@@ -225,8 +225,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/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 410d9de..7fe178c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.screenshot;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,13 +58,17 @@
         public int availableBottom = Integer.MAX_VALUE;
         // If true, return an empty rect any time a partial result would have been returned.
         public boolean emptyInsteadOfPartial = false;
+        private int mPreviousTopRequested = 0;
 
         @Override
         public ListenableFuture<ScrollCaptureClient.CaptureResult> requestTile(int top) {
+            // Ensure we don't request a tile more than a tile away.
+            assertTrue(Math.abs(top - mPreviousTopRequested) <= getTileHeight());
+            mPreviousTopRequested = top;
             Rect requested = new Rect(0, top, getPageWidth(), top + getTileHeight());
             Rect fullContent = new Rect(0, availableTop, getPageWidth(), availableBottom);
             Rect captured = new Rect(requested);
-            captured.intersect(fullContent);
+            assertTrue(captured.intersect(fullContent));
             if (emptyInsteadOfPartial && captured.height() != getTileHeight()) {
                 captured = new Rect();
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0bf1ac3..e65db5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar
 
 import android.app.WallpaperManager
+import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.Choreographer
@@ -64,6 +65,7 @@
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var root: View
     @Mock private lateinit var viewRootImpl: ViewRootImpl
+    @Mock private lateinit var windowToken: IBinder
     @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
@@ -80,6 +82,7 @@
     @Before
     fun setup() {
         `when`(root.viewRootImpl).thenReturn(viewRootImpl)
+        `when`(root.windowToken).thenReturn(windowToken)
         `when`(root.isAttachedToWindow).thenReturn(true)
         `when`(statusBarStateController.state).then { statusBarState }
         `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
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 91f3611..6a5e6e8 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
@@ -66,6 +66,7 @@
 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;
@@ -206,10 +207,16 @@
     @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;
@@ -296,6 +303,10 @@
                 .thenReturn(mKeyguardClockSwitchController);
         when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
                 .thenReturn(mKeyguardStatusViewController);
+        when(mKeyguardStatusBarViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusBarViewComponent);
+        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+                .thenReturn(mKeyguardStatusBarViewController);
 
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
@@ -314,6 +325,7 @@
                 mKeyguardStatusViewComponentFactory,
                 mKeyguardQsUserSwitchComponentFactory,
                 mKeyguardUserSwitcherComponentFactory,
+                mKeyguardStatusBarViewComponentFactory,
                 mQSDetailDisplayer,
                 mGroupManager,
                 mNotificationAreaController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 8f36415..ef33172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -76,6 +76,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.telephony.TelephonyListenerManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -113,6 +114,7 @@
     protected NetworkScoreManager mMockNsm;
     protected SubscriptionManager mMockSm;
     protected TelephonyManager mMockTm;
+    protected TelephonyListenerManager mTelephonyListenerManager;
     protected BroadcastDispatcher mMockBd;
     protected Config mConfig;
     protected CallbackHandler mCallbackHandler;
@@ -164,6 +166,7 @@
         mDemoModeController = mock(DemoModeController.class);
         mMockWm = mock(WifiManager.class);
         mMockTm = mock(TelephonyManager.class);
+        mTelephonyListenerManager = mock(TelephonyListenerManager.class);
         mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
         mMockBd = mock(BroadcastDispatcher.class);
@@ -213,6 +216,7 @@
         mNetworkController = new NetworkControllerImpl(mContext,
                 mMockCm,
                 mMockTm,
+                mTelephonyListenerManager,
                 mMockWm,
                 mMockNsm,
                 mMockSm,
@@ -285,7 +289,8 @@
     protected NetworkControllerImpl setUpNoMobileData() {
         when(mMockTm.isDataCapable()).thenReturn(false);
         NetworkControllerImpl networkControllerNoMobile =
-                new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
+                new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager,
+                        mMockWm, mMockNsm, mMockSm,
                         mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index b108dd8..f4ad819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -106,7 +106,8 @@
     public void test4gDataIcon() {
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+                mTelephonyListenerManager, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 91e9f06..3c5cbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -61,8 +61,9 @@
         // Turn off mobile network support.
         when(mMockTm.isDataCapable()).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
-                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+                mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+                Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
                 mDemoModeController);
@@ -80,8 +81,9 @@
         when(mMockTm.getServiceState()).thenReturn(mServiceState);
         when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList());
 
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
-                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+                mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+                Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
                 mDemoModeController);
@@ -147,8 +149,9 @@
         // Turn off mobile network support.
         when(mMockTm.isDataCapable()).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
-                mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+                mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+                Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
                 mDemoModeController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
new file mode 100644
index 0000000..463b336
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TelephonyCallbackTest extends SysuiTestCase {
+
+    private TelephonyCallback mTelephonyCallback = new TelephonyCallback();
+    
+    @Test
+    public void testAddListener_ActiveDataSubscriptionIdListener() {
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+        mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+    }
+
+    @Test
+    public void testAddListener_CallStateListener() {
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+        mTelephonyCallback.addCallStateListener(state -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.addCallStateListener(state -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+    }
+
+    @Test
+    public void testAddListener_ServiceStateListener() {
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+        mTelephonyCallback.addServiceStateListener(serviceState -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.addServiceStateListener(serviceState -> {});
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+    }
+
+    @Test
+    public void testRemoveListener_ActiveDataSubscriptionIdListener() {
+        ActiveDataSubscriptionIdListener listener = subId -> {};
+        mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+        mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+    }
+
+    @Test
+    public void testRemoveListener_CallStateListener() {
+        CallStateListener listener = state -> {};
+        mTelephonyCallback.addCallStateListener(listener);
+        mTelephonyCallback.addCallStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeCallStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeCallStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+    }
+
+    @Test
+    public void testRemoveListener_ServiceStateListener() {
+        ServiceStateListener listener = serviceState -> {};
+        mTelephonyCallback.addServiceStateListener(listener);
+        mTelephonyCallback.addServiceStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeServiceStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+        mTelephonyCallback.removeServiceStateListener(listener);
+        assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
new file mode 100644
index 0000000..0d1ac7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.telephony;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TelephonyListenerManagerTest extends SysuiTestCase {
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    @Mock
+    private TelephonyCallback mTelephonyCallback;
+
+    TelephonyListenerManager mTelephonyListenerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTelephonyListenerManager = new TelephonyListenerManager(
+                mTelephonyManager, mExecutor, mTelephonyCallback);
+    }
+
+    @Test
+    public void testAddListenerRegisters_ActiveDataSubscriptionIdListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+    }
+
+    @Test
+    public void testAddListenerRegisters_CallStateListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+    }
+
+    @Test
+    public void testAddListenerRegisters_ServiceStateListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+    }
+
+    @Test
+    public void testAddListenerRegisters_mixed() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        mTelephonyListenerManager.addCallStateListener(state -> {});
+        mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+    }
+
+    @Test
+    public void testRemoveListenerUnregisters_ActiveDataSubscriptionIdListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        ActiveDataSubscriptionIdListener mListener = subId -> { };
+
+        // Need to add one to actually register
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+        reset(mTelephonyManager);
+
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .unregisterTelephonyCallback(mTelephonyCallback);
+    }
+
+    @Test
+    public void testRemoveListenerUnregisters_CallStateListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        CallStateListener mListener = state -> { };
+
+        // Need to add one to actually register
+        mTelephonyListenerManager.addCallStateListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+        reset(mTelephonyManager);
+
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+        mTelephonyListenerManager.removeCallStateListener(mListener);
+        mTelephonyListenerManager.removeCallStateListener(mListener);
+        mTelephonyListenerManager.removeCallStateListener(mListener);
+        mTelephonyListenerManager.removeCallStateListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .unregisterTelephonyCallback(mTelephonyCallback);
+    }
+
+    @Test
+    public void testRemoveListenerUnregisters_ServiceStateListener() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        ServiceStateListener mListener = serviceState -> { };
+
+        // Need to add one to actually register
+        mTelephonyListenerManager.addServiceStateListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+        reset(mTelephonyManager);
+
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+        mTelephonyListenerManager.removeServiceStateListener(mListener);
+        mTelephonyListenerManager.removeServiceStateListener(mListener);
+        mTelephonyListenerManager.removeServiceStateListener(mListener);
+        mTelephonyListenerManager.removeServiceStateListener(mListener);
+        verify(mTelephonyManager, times(1))
+                .unregisterTelephonyCallback(mTelephonyCallback);
+    }
+
+    @Test
+    public void testRemoveListenerUnregisters_mixed() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        ActiveDataSubscriptionIdListener mListenerA = subId -> { };
+        ServiceStateListener mListenerB = serviceState -> { };
+        CallStateListener mListenerC = state -> { };
+
+        // Need to add one to actually register
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListenerA);
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+        reset(mTelephonyManager);
+
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA);
+        mTelephonyListenerManager.removeServiceStateListener(mListenerB);
+        mTelephonyListenerManager.removeCallStateListener(mListenerC);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA);
+        mTelephonyListenerManager.removeServiceStateListener(mListenerB);
+        mTelephonyListenerManager.removeCallStateListener(mListenerC);
+        verify(mTelephonyManager, times(1))
+                .unregisterTelephonyCallback(mTelephonyCallback);
+    }
+
+    @Test
+    public void testAddListener_noDoubleRegister() {
+        when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        verify(mTelephonyManager, times(1))
+                .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+
+        reset(mTelephonyManager);
+
+        // A second call to add doesn't register another listener.
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+        verify(mTelephonyManager, never()).registerTelephonyCallback(mExecutor, mTelephonyCallback);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
index 7c7ad53..d3d30f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
@@ -27,6 +27,7 @@
     private final FakeSystemClock mClock;
     private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
     private boolean mIgnoreClockUpdates;
+    private boolean mExecuting;
 
     /**
      * Initializes a fake executor.
@@ -56,7 +57,9 @@
      */
     public boolean runNextReady() {
         if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) {
+            mExecuting = true;
             mQueuedRunnables.poll().mRunnable.run();
+            mExecuting = false;
             return true;
         }
 
@@ -162,6 +165,10 @@
         executeDelayed(command, 0);
     }
 
+    public boolean isExecuting() {
+        return mExecuting;
+    }
+
     /**
      * Run all Executors in a loop until they all report they have no ready work to do.
      *
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index abc283f..87206c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.util.concurrency;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -319,6 +321,18 @@
         assertEquals(1, runnable.mRunCount);
     }
 
+    @Test
+    public void testIsExecuting() {
+        FakeSystemClock clock = new FakeSystemClock();
+        FakeExecutor fakeExecutor = new FakeExecutor(clock);
+
+        Runnable runnable = () -> assertThat(fakeExecutor.isExecuting()).isTrue();
+
+        assertThat(fakeExecutor.isExecuting()).isFalse();
+        fakeExecutor.execute(runnable);
+        assertThat(fakeExecutor.isExecuting()).isFalse();
+    }
+
     private static class RunnableImpl implements Runnable {
         int mRunCount;
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5f7016e..641b38d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -235,6 +235,7 @@
         "java/com/android/server/connectivity/NetworkAgentInfo.java",
         "java/com/android/server/connectivity/NetworkDiagnostics.java",
         "java/com/android/server/connectivity/NetworkNotificationManager.java",
+        "java/com/android/server/connectivity/NetworkOffer.java",
         "java/com/android/server/connectivity/NetworkRanker.java",
         "java/com/android/server/connectivity/OsCompat.java",
         "java/com/android/server/connectivity/PermissionMonitor.java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c295778..a9eb2c1 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1141,4 +1141,9 @@
      */
     public abstract boolean isPackageFrozen(
             @NonNull String packageName, int callingUid, int userId);
+
+    /**
+     * Deletes the OAT artifacts of a package.
+     */
+    public abstract void deleteOatArtifactsOfPackage(String packageName);
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fbba189..67cd6c3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -15,7 +15,6 @@
  */
 
 package com.android.server;
-
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -122,6 +121,7 @@
 import android.net.INetworkAgent;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
+import android.net.INetworkOfferCallback;
 import android.net.IOnCompleteListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
@@ -229,6 +229,7 @@
 import com.android.server.connectivity.AutodestructReference;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
+import com.android.server.connectivity.FullScore;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -236,6 +237,7 @@
 import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import com.android.server.connectivity.NetworkOffer;
 import com.android.server.connectivity.NetworkRanker;
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProfileNetworkPreferences;
@@ -593,6 +595,18 @@
     private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
 
     /**
+     * Event to register a new network offer
+     * obj = NetworkOffer
+     */
+    private static final int EVENT_REGISTER_NETWORK_OFFER = 52;
+
+    /**
+     * Event to unregister an existing network offer
+     * obj = INetworkOfferCallback
+     */
+    private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -1145,8 +1159,8 @@
         /**
          * @see NetworkUtils#queryUserAccess(int, int)
          */
-        public boolean queryUserAccess(int uid, int netId) {
-            return NetworkUtils.queryUserAccess(uid, netId);
+        public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) {
+            return cs.queryUserAccess(uid, network);
         }
 
         /**
@@ -1203,7 +1217,7 @@
         mNetworkRanker = new NetworkRanker();
         final NetworkRequest defaultInternetRequest = createDefaultRequest();
         mDefaultRequest = new NetworkRequestInfo(
-                defaultInternetRequest, null,
+                Process.myUid(), defaultInternetRequest, null,
                 new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
                 null /* attributionTags */);
         mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
@@ -1409,8 +1423,7 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    networkRequest, null,
-                    new Binder(),
+                    Process.myUid(), networkRequest, null, new Binder(),
                     NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
                     null /* attributionTags */));
         } else {
@@ -1563,7 +1576,7 @@
         final int requestId = nri.getActiveRequest() != null
                 ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
         mNetworkInfoBlockingLogs.log(String.format(
-                "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
+                "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId()));
     }
 
     /**
@@ -2078,6 +2091,8 @@
     private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc,
             int callerUid, String callerPackageName) {
         if (!checkSettingsPermission()) {
+            // There is no need to track the effective UID of the request here. If the caller lacks
+            // the settings permission, the effective UID is the same as the calling ID.
             nc.setSingleUid(callerUid);
         }
         nc.setRequestorUidAndPackageName(callerUid, callerPackageName);
@@ -4564,6 +4579,18 @@
                     handleUnregisterNetworkProvider((Messenger) msg.obj);
                     break;
                 }
+                case EVENT_REGISTER_NETWORK_OFFER: {
+                    handleRegisterNetworkOffer((NetworkOffer) msg.obj);
+                    break;
+                }
+                case EVENT_UNREGISTER_NETWORK_OFFER: {
+                    final NetworkOfferInfo offer =
+                            findNetworkOfferInfoByCallback((INetworkOfferCallback) msg.obj);
+                    if (null != offer) {
+                        handleUnregisterNetworkOffer(offer);
+                    }
+                    break;
+                }
                 case EVENT_REGISTER_NETWORK_AGENT: {
                     final Pair<NetworkAgentInfo, INetworkMonitor> arg =
                             (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
@@ -4839,6 +4866,42 @@
         nai.networkMonitor().forceReevaluation(uid);
     }
 
+    // TODO: call into netd.
+    private boolean queryUserAccess(int uid, Network network) {
+        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai == null) return false;
+
+        // Any UID can use its default network.
+        if (nai == getDefaultNetworkForUid(uid)) return true;
+
+        // Privileged apps can use any network.
+        if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
+            return true;
+        }
+
+        // An unprivileged UID can use a VPN iff the VPN applies to it.
+        if (nai.isVPN()) {
+            return nai.networkCapabilities.appliesToUid(uid);
+        }
+
+        // An unprivileged UID can bypass the VPN that applies to it only if it can protect its
+        // sockets, i.e., if it is the owner.
+        final NetworkAgentInfo vpn = getVpnForUid(uid);
+        if (vpn != null && !vpn.networkAgentConfig.allowBypass
+                && uid != vpn.networkCapabilities.getOwnerUid()) {
+            return false;
+        }
+
+        // The UID's permission must be at least sufficient for the network. Since the restricted
+        // permission was already checked above, that just leaves background networks.
+        if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
+            return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
+        }
+
+        // Unrestricted network. Anyone gets to use it.
+        return true;
+    }
+
     /**
      * Returns information about the proxy a certain network is using. If given a null network, it
      * it will return the proxy for the bound network for the caller app or the default proxy if
@@ -4859,7 +4922,7 @@
                 return null;
             }
             return getLinkPropertiesProxyInfo(activeNetwork);
-        } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) {
+        } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) {
             // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
             // caller may not have.
             return getLinkPropertiesProxyInfo(network);
@@ -5364,6 +5427,8 @@
         boolean mPendingIntentSent;
         @Nullable
         final Messenger mMessenger;
+
+        // Information about the caller that caused this object to be created.
         @Nullable
         private final IBinder mBinder;
         final int mPid;
@@ -5371,6 +5436,13 @@
         final @NetworkCallback.Flag int mCallbackFlags;
         @Nullable
         final String mCallingAttributionTag;
+
+        // Effective UID of this request. This is different from mUid when a privileged process
+        // files a request on behalf of another UID. This UID is used to determine blocked status,
+        // UID matching, and so on. mUid above is used for permission checks and to enforce the
+        // maximum limit of registered callbacks per UID.
+        final int mAsUid;
+
         // 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
@@ -5398,12 +5470,12 @@
             return (null == uids) ? new ArraySet<>() : uids;
         }
 
-        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
-                @Nullable String callingAttributionTag) {
-            this(Collections.singletonList(r), r, pi, callingAttributionTag);
+        NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r,
+                @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
+            this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag);
         }
 
-        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+        NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
                 @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
                 @Nullable String callingAttributionTag) {
             ensureAllNetworkRequestsHaveType(r);
@@ -5414,6 +5486,7 @@
             mBinder = null;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
+            mAsUid = asUid;
             mNetworkRequestCounter.incrementCountOrThrow(mUid);
             /**
              * Location sensitive data not included in pending intent. Only included in
@@ -5423,14 +5496,15 @@
             mCallingAttributionTag = callingAttributionTag;
         }
 
-        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+        NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
                 @Nullable final IBinder binder,
                 @NetworkCallback.Flag int callbackFlags,
                 @Nullable String callingAttributionTag) {
-            this(Collections.singletonList(r), r, m, binder, callbackFlags, callingAttributionTag);
+            this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
+                    callingAttributionTag);
         }
 
-        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+        NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
                 @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
                 @Nullable final IBinder binder,
                 @NetworkCallback.Flag int callbackFlags,
@@ -5443,6 +5517,7 @@
             mBinder = binder;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
+            mAsUid = asUid;
             mPendingIntent = null;
             mNetworkRequestCounter.incrementCountOrThrow(mUid);
             mCallbackFlags = callbackFlags;
@@ -5485,18 +5560,19 @@
             mBinder = nri.mBinder;
             mPid = nri.mPid;
             mUid = nri.mUid;
+            mAsUid = nri.mAsUid;
             mPendingIntent = nri.mPendingIntent;
             mNetworkRequestCounter.incrementCountOrThrow(mUid);
             mCallbackFlags = nri.mCallbackFlags;
             mCallingAttributionTag = nri.mCallingAttributionTag;
         }
 
-        NetworkRequestInfo(@NonNull final NetworkRequest r) {
-            this(Collections.singletonList(r));
+        NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
+            this(asUid, Collections.singletonList(r));
         }
 
-        NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
-            this(r, r.get(0), null /* pi */, null /* callingAttributionTag */);
+        NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r) {
+            this(asUid, 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
@@ -5532,9 +5608,10 @@
 
         @Override
         public String toString() {
-            return "uid/pid:" + mUid + "/" + mPid + " active request Id: "
+            final String asUidString = (mAsUid == mUid) ? "" : " asUid: " + mAsUid;
+            return "uid/pid:" + mUid + "/" + mPid + asUidString + " activeRequest: "
                     + (mActiveRequest == null ? null : mActiveRequest.requestId)
-                    + " callback request Id: "
+                    + " callbackRequest: "
                     + mNetworkRequestForCallback.requestId
                     + " " + mRequests
                     + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
@@ -5635,7 +5712,7 @@
     }
 
     @Override
-    public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
+    public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
             int reqTypeInt, Messenger messenger, int timeoutMs, IBinder binder,
             int legacyType, int callbackFlags, @NonNull String callingPackageName,
             @Nullable String callingAttributionTag) {
@@ -5647,6 +5724,12 @@
         }
         final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
         final int callingUid = mDeps.getCallingUid();
+        // Privileged callers can track the default network of another UID by passing in a UID.
+        if (asUid != Process.INVALID_UID) {
+            enforceSettingsPermission();
+        } else {
+            asUid = callingUid;
+        }
         final NetworkRequest.Type reqType;
         try {
             reqType = NetworkRequest.Type.values()[reqTypeInt];
@@ -5656,10 +5739,10 @@
         switch (reqType) {
             case TRACK_DEFAULT:
                 // 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.
+                // is unused and will be replaced by ones appropriate for the UID (usually, the
+                // calling app). This allows callers to keep track of the default network.
                 networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
-                        defaultNc, callingUid, callingPackageName);
+                        defaultNc, asUid, callingUid, callingPackageName);
                 enforceAccessPermission();
                 break;
             case TRACK_SYSTEM_DEFAULT:
@@ -5711,7 +5794,8 @@
         final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
         final NetworkRequestInfo nri = getNriToRegister(
-                networkRequest, messenger, binder, callbackFlags, callingAttributionTag);
+                asUid, networkRequest, messenger, binder, callbackFlags,
+                callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
         // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5738,25 +5822,27 @@
      * 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 asUid the uid on behalf of which to file the request. Different from requestorUid
+     *              when a privileged caller is tracking the default network for another uid.
      * @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,
+    private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
             @Nullable final Messenger msgr, @Nullable final IBinder binder,
             @NetworkCallback.Flag int callbackFlags,
             @Nullable String callingAttributionTag) {
         final List<NetworkRequest> requests;
         if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
             requests = copyDefaultNetworkRequestsForUid(
-                    nr.getRequestorUid(), nr.getRequestorPackageName());
+                    asUid, nr.getRequestorUid(), nr.getRequestorPackageName());
         } else {
             requests = Collections.singletonList(nr);
         }
         return new NetworkRequestInfo(
-                requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+                asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
     }
 
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
@@ -5837,8 +5923,8 @@
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
-        NetworkRequestInfo nri =
-                new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
+        NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
+                callingAttributionTag);
         if (DBG) log("pendingRequest for " + nri);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
                 nri));
@@ -5905,7 +5991,7 @@
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
         NetworkRequestInfo nri =
-                new NetworkRequestInfo(networkRequest, messenger, binder, callbackFlags,
+                new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
                         callingAttributionTag);
         if (VDBG) log("listenForNetwork for " + nri);
 
@@ -5930,8 +6016,8 @@
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
-        NetworkRequestInfo nri =
-                new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
+        NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
+                callingAttributionTag);
         if (VDBG) log("pendingListenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -5986,12 +6072,37 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
+    @Override
+    public void offerNetwork(final int providerId,
+            @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
+            @NonNull final INetworkOfferCallback callback) {
+        final NetworkOffer offer = new NetworkOffer(
+                FullScore.makeProspectiveScore(score, caps), caps, callback, providerId);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer));
+    }
+
+    @Override
+    public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_OFFER, callback));
+    }
+
     private void handleUnregisterNetworkProvider(Messenger messenger) {
         NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
         if (npi == null) {
             loge("Failed to find Messenger in unregisterNetworkProvider");
             return;
         }
+        // Unregister all the offers from this provider
+        final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>();
+        for (final NetworkOfferInfo noi : mNetworkOffers) {
+            if (noi.offer.providerId == npi.providerId) {
+                // Can't call handleUnregisterNetworkOffer here because iteration is in progress
+                toRemove.add(noi);
+            }
+        }
+        for (NetworkOfferInfo noi : toRemove) {
+            handleUnregisterNetworkOffer(noi);
+        }
         if (DBG) log("unregisterNetworkProvider for " + npi.name);
     }
 
@@ -6030,6 +6141,10 @@
     // (on the handler thread).
     private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>();
 
+    // Must only be accessed on the handler thread
+    @NonNull
+    private final ArrayList<NetworkOfferInfo> mNetworkOffers = new ArrayList<>();
+
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
@@ -6081,33 +6196,37 @@
     /**
      * Get a copy of the network requests of the default request that is currently tracking the
      * given uid.
+     * @param asUid the uid on behalf of which to file the request. Different from requestorUid
+     *              when a privileged caller is tracking the default network for another 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) {
+            final int asUid, final int requestorUid, @NonNull final String requestorPackageName) {
         return copyNetworkRequestsForUid(
-                getDefaultRequestTrackingUid(requestorUid).mRequests,
-                requestorUid, requestorPackageName);
+                getDefaultRequestTrackingUid(asUid).mRequests,
+                asUid, requestorUid, requestorPackageName);
     }
 
     /**
      * Copy the given nri's NetworkRequest collection.
      * @param requestsToCopy the NetworkRequest collection to be copied.
+     * @param asUid the uid on behalf of which to file the request. Different from requestorUid
+     *              when a privileged caller is tracking the default network for another uid.
      * @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) {
+            @NonNull final List<NetworkRequest> requestsToCopy, final int asUid,
+            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.networkCapabilities, asUid, requestorUid, requestorPackageName),
                     nr.legacyType, nextNetworkRequestId(), nr.type));
         }
         return requests;
@@ -6115,17 +6234,17 @@
 
     @NonNull
     private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
-            @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
-            @NonNull final String requestorPackageName) {
+            @NonNull final NetworkCapabilities netCapToCopy, final int asUid,
+            final int requestorUid, @NonNull final String requestorPackageName) {
         // These capabilities are for a TRACK_DEFAULT callback, so:
         // 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between
         //    mDefaultRequest and a per-UID default request.
         //    TODO: stop depending on the fact that these two unrelated things happen to be the same
-        // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will
+        // 2. Always set the UIDs to asUid. restrictRequestUidsForCallerAndSetRequestorInfo will
         //    not do this in the case of a privileged application.
         final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
-        netCap.setSingleUid(requestorUid);
+        netCap.setSingleUid(asUid);
         restrictRequestUidsForCallerAndSetRequestorInfo(
                 netCap, requestorUid, requestorPackageName);
         return netCap;
@@ -6335,6 +6454,71 @@
         updateUids(nai, null, nai.networkCapabilities);
     }
 
+    private class NetworkOfferInfo implements IBinder.DeathRecipient {
+        @NonNull public final NetworkOffer offer;
+
+        NetworkOfferInfo(@NonNull final NetworkOffer offer) {
+            this.offer = offer;
+        }
+
+        @Override
+        public void binderDied() {
+            mHandler.post(() -> handleUnregisterNetworkOffer(this));
+        }
+    }
+
+    private boolean isNetworkProviderWithIdRegistered(final int providerId) {
+        for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            if (npi.providerId == providerId) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Register or update a network offer.
+     * @param newOffer The new offer. If the callback member is the same as an existing
+     *                 offer, it is an update of that offer.
+     */
+    private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) {
+        ensureRunningOnConnectivityServiceThread();
+        if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
+            // This may actually happen if a provider updates its score or registers and then
+            // immediately unregisters. The offer would still be in the handler queue, but the
+            // provider would have been removed.
+            if (DBG) log("Received offer from an unregistered provider");
+            return;
+        }
+        final NetworkOfferInfo existingOffer = findNetworkOfferInfoByCallback(newOffer.callback);
+        if (null != existingOffer) {
+            handleUnregisterNetworkOffer(existingOffer);
+            newOffer.migrateFrom(existingOffer.offer);
+        }
+        final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer);
+        try {
+            noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */);
+        } catch (RemoteException e) {
+            noi.binderDied();
+            return;
+        }
+        mNetworkOffers.add(noi);
+        // TODO : send requests to the provider.
+    }
+
+    private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) {
+        ensureRunningOnConnectivityServiceThread();
+        mNetworkOffers.remove(noi);
+        noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */);
+    }
+
+    @Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback(
+            @NonNull final INetworkOfferCallback callback) {
+        ensureRunningOnConnectivityServiceThread();
+        for (final NetworkOfferInfo noi : mNetworkOffers) {
+            if (noi.offer.callback.equals(callback)) return noi;
+        }
+        return null;
+    }
+
     /**
      * Called when receiving LinkProperties directly from a NetworkAgent.
      * Stores into |nai| any data coming from the agent that might also be written to the network's
@@ -8031,9 +8215,9 @@
 
         final boolean metered = nai.networkCapabilities.isMetered();
         boolean blocked;
-        blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges);
+        blocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
         blocked |= NetworkPolicyManager.isUidBlocked(
-                mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered);
+                mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE), metered);
         callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
     }
 
@@ -8061,12 +8245,12 @@
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
             final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked;
 
-            oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges);
+            oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges);
             newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
-                    ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges)
+                    ? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
                     : oldVpnBlocked;
 
-            final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE);
+            final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
             oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked(
                     blockedReasons, oldMetered);
             newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked(
@@ -8101,7 +8285,7 @@
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
                 NetworkRequest nr = nai.requestAt(i);
                 NetworkRequestInfo nri = mNetworkRequests.get(nr);
-                if (nri != null && nri.mUid == uid) {
+                if (nri != null && nri.mAsUid == uid) {
                     callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg);
                 }
             }
@@ -8866,7 +9050,7 @@
         // nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in
         // handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
         // callback's binder death.
-        final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId);
+        final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
         final ConnectivityDiagnosticsCallbackInfo cbInfo =
                 new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
 
@@ -9350,7 +9534,7 @@
             nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
             nrs.add(createDefaultRequest());
             setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
-            final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
+            final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs);
             result.add(nri);
         }
         return result;
@@ -9521,7 +9705,7 @@
             }
             // Include this nri if it will be tracked by the new per-app default requests.
             final boolean isNriGoingToBeTracked =
-                    getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest;
+                    getDefaultRequestTrackingUid(nri.mAsUid) != mDefaultRequest;
             if (isNriGoingToBeTracked) {
                 defaultCallbackRequests.add(nri);
             }
@@ -9543,7 +9727,7 @@
         final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
         for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
             final NetworkRequestInfo trackingNri =
-                    getDefaultRequestTrackingUid(callbackRequest.mUid);
+                    getDefaultRequestTrackingUid(callbackRequest.mAsUid);
 
             // If this nri is not being tracked, the change it back to an untracked nri.
             if (trackingNri == mDefaultRequest) {
@@ -9553,12 +9737,12 @@
                 continue;
             }
 
-            final String requestorPackageName =
-                    callbackRequest.mRequests.get(0).getRequestorPackageName();
+            final NetworkRequest request = callbackRequest.mRequests.get(0);
             callbackRequestsToRegister.add(new NetworkRequestInfo(
                     callbackRequest,
                     copyNetworkRequestsForUid(
-                            trackingNri.mRequests, callbackRequest.mUid, requestorPackageName)));
+                            trackingNri.mRequests, callbackRequest.mAsUid,
+                            callbackRequest.mUid, request.getRequestorPackageName())));
         }
         return callbackRequestsToRegister;
     }
@@ -9662,7 +9846,7 @@
                 ranges.add(new UidRange(uid, uid));
             }
             setNetworkRequestUids(requests, ranges);
-            return new NetworkRequestInfo(requests);
+            return new NetworkRequestInfo(Process.myUid(), requests);
         }
 
         private NetworkRequest createUnmeteredNetworkRequest() {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7f96aff..09f4c22 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -143,7 +143,6 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.os.SomeArgs;
-import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
@@ -3377,19 +3376,28 @@
     }
 
     @Override
-    public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) {
+    public void notifyAppIoBlocked(String volumeUuid, int uid, int tid,
+            @StorageManager.AppIoBlockedReason int reason) {
         enforceExternalStorageService();
 
         mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
     }
 
     @Override
-    public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) {
+    public void notifyAppIoResumed(String volumeUuid, int uid, int tid,
+            @StorageManager.AppIoBlockedReason int reason) {
         enforceExternalStorageService();
 
         mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
     }
 
+    @Override
+    public boolean isAppIoBlocked(String volumeUuid, int uid, int tid,
+            @StorageManager.AppIoBlockedReason int reason) {
+        return isAppIoBlocked(uid);
+    }
+
+
     private boolean isAppIoBlocked(int uid) {
         return mStorageSessionController.isAppIoBlocked(uid);
     }
@@ -4259,31 +4267,37 @@
         }
     }
 
+    @Override
+    public int getExternalStorageMountMode(int uid, String packageName) {
+        enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
+        return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
+    }
+
     private int getMountModeInternal(int uid, String packageName) {
         try {
             // Get some easy cases out of the way first
             if (Process.isIsolated(uid)) {
-                return Zygote.MOUNT_EXTERNAL_NONE;
+                return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
             }
 
             final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
             if (ArrayUtils.isEmpty(packagesForUid)) {
                 // It's possible the package got uninstalled already, so just ignore.
-                return Zygote.MOUNT_EXTERNAL_NONE;
+                return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
             }
             if (packageName == null) {
                 packageName = packagesForUid[0];
             }
 
             if (mPmInternal.isInstantApp(packageName, UserHandle.getUserId(uid))) {
-                return Zygote.MOUNT_EXTERNAL_NONE;
+                return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
             }
 
             if (mStorageManagerInternal.isExternalStorageService(uid)) {
                 // Determine if caller requires pass_through mount; note that we do this for
                 // all processes that share a UID with MediaProvider; but this is fine, since
                 // those processes anyway share the same rights as MediaProvider.
-                return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+                return StorageManager.MOUNT_MODE_EXTERNAL_PASS_THROUGH;
             }
 
             if ((mDownloadsAuthorityAppId == UserHandle.getAppId(uid)
@@ -4291,7 +4305,7 @@
                 // DownloadManager can write in app-private directories on behalf of apps;
                 // give it write access to Android/
                 // ExternalStorageProvider can access Android/{data,obb} dirs in managed mode
-                return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+                return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
             }
 
             final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
@@ -4301,7 +4315,7 @@
                         0, UserHandle.getUserId(uid));
                 if (ai != null && ai.isSignedWithPlatformKey()) {
                     // Platform processes hosting the MTP server should be able to write in Android/
-                    return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+                    return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
                 }
             }
 
@@ -4326,13 +4340,13 @@
                 }
             }
             if ((hasInstall || hasInstallOp) && hasWrite) {
-                return Zygote.MOUNT_EXTERNAL_INSTALLER;
+                return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER;
             }
-            return Zygote.MOUNT_EXTERNAL_DEFAULT;
+            return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT;
         } catch (RemoteException e) {
             // Should not happen
         }
-        return Zygote.MOUNT_EXTERNAL_NONE;
+        return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
     }
 
     private static class Callbacks extends Handler {
@@ -4702,7 +4716,8 @@
                 return true;
             }
 
-            return getExternalStorageMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+            return getExternalStorageMountMode(uid, packageName)
+                    != StorageManager.MOUNT_MODE_EXTERNAL_NONE;
         }
 
         private void killAppForOpChange(int code, int uid) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7276c78..411fc67 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -154,6 +154,7 @@
         int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
         int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        int targetSdk;
 
         boolean matchTelephonyCallbackEvent(int event) {
             return (callback != null) && (this.eventList.contains(event));
@@ -919,6 +920,8 @@
             }
             r.phoneId = phoneId;
             r.eventList = events;
+            r.targetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage);
+
             if (DBG) {
                 log("listen:  Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
             }
@@ -1748,6 +1751,11 @@
                             TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)
                             && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
                         try {
+                            if (r.targetSdk <= android.os.Build.VERSION_CODES.R) {
+                                telephonyDisplayInfo =
+                                        getBackwardCompatibleTelephonyDisplayInfo(
+                                                telephonyDisplayInfo);
+                            }
                             r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
@@ -1759,6 +1767,19 @@
         }
     }
 
+    private TelephonyDisplayInfo getBackwardCompatibleTelephonyDisplayInfo(
+            @NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+        int networkType = telephonyDisplayInfo.getNetworkType();
+        int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
+        if (networkType == TelephonyManager.NETWORK_TYPE_NR) {
+            overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        } else if (networkType == TelephonyManager.NETWORK_TYPE_LTE
+                && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
+            overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
+        }
+        return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+    }
+
     public void notifyCallForwardingChanged(boolean cfi) {
         notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
     }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 051cd99..cfa110e 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -348,7 +348,8 @@
         }
     }
 
-    private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) {
+    private void enforceCallingUserAndCarrierPrivilege(
+            ParcelUuid subscriptionGroup, String pkgName) {
         // Only apps running in the primary (system) user are allowed to configure the VCN. This is
         // in line with Telephony's behavior with regards to binding to a Carrier App provided
         // CarrierConfigService.
@@ -362,12 +363,15 @@
                     subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup));
                 });
 
-        final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
         for (SubscriptionInfo info : subscriptionInfos) {
+            final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
+                    .createForSubscriptionId(info.getSubscriptionId());
+
             // Check subscription is active first; much cheaper/faster check, and an app (currently)
             // cannot be carrier privileged for inactive subscriptions.
             if (subMgr.isValidSlotIndex(info.getSimSlotIndex())
-                    && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) {
+                    && telMgr.checkCarrierPrivilegesForPackage(pkgName)
+                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 // TODO (b/173717728): Allow configuration for inactive, but manageable
                 // subscriptions.
                 // TODO (b/173718661): Check for whole subscription groups at a time.
@@ -535,7 +539,7 @@
 
         mContext.getSystemService(AppOpsManager.class)
                 .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
-        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
 
         Binder.withCleanCallingIdentity(() -> {
             synchronized (mLock) {
@@ -553,11 +557,14 @@
      * <p>Implements the IVcnManagementService Binder interface.
      */
     @Override
-    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
+    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+        requireNonNull(opPkgName, "opPkgName was null");
         Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
 
-        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+        mContext.getSystemService(AppOpsManager.class)
+                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
+        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
 
         Binder.withCleanCallingIdentity(() -> {
             synchronized (mLock) {
@@ -820,8 +827,7 @@
 
             final IBinder cbBinder = callback.asBinder();
             final VcnStatusCallbackInfo cbInfo =
-                    new VcnStatusCallbackInfo(
-                            subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+                    new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid);
 
             try {
                 cbBinder.linkToDeath(cbInfo, 0 /* flags */);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 9636641..211999f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -35,11 +35,11 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.sysprop.WatchdogProperties;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.sysprop.WatchdogProperties;
 
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.ZygoteConnectionConstants;
@@ -56,9 +56,9 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
 import java.util.HashSet;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /** This class calls its monitor every minute. Killing this process if they don't return **/
 public class Watchdog {
@@ -688,7 +688,7 @@
                         if (mActivity != null) {
                             mActivity.addErrorToDropBox(
                                     "watchdog", null, "system_server", null, null, null,
-                                    subject, report.toString(), stack, null);
+                                    subject, report.toString(), stack, null, null, null);
                         }
                         FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED,
                                 subject);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 492759f..321e3b1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7706,9 +7706,8 @@
      */
     void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
             ApplicationErrorReport.CrashInfo crashInfo) {
-        boolean isIncremental = false;
         float loadingProgress = 1;
-        long millisSinceOldestPendingRead = 0;
+        IncrementalMetrics incrementalMetrics = null;
         // Notify package manager service to possibly update package state
         if (r != null && r.info != null && r.info.packageName != null) {
             final String codePath = r.info.getCodePath();
@@ -7719,8 +7718,7 @@
             if (incrementalStatesInfo != null) {
                 loadingProgress = incrementalStatesInfo.getProgress();
             }
-            isIncremental = IncrementalManager.isIncrementalPath(codePath);
-            if (isIncremental) {
+            if (IncrementalManager.isIncrementalPath(codePath)) {
                 // Report in the main log about the incremental package
                 Slog.e(TAG, "App crashed on incremental package " + r.info.packageName
                         + " which is " + ((int) (loadingProgress * 100)) + "% loaded.");
@@ -7729,8 +7727,7 @@
                 if (incrementalService != null) {
                     final IncrementalManager incrementalManager = new IncrementalManager(
                             IIncrementalService.Stub.asInterface(incrementalService));
-                    IncrementalMetrics metrics = incrementalManager.getMetrics(codePath);
-                    millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead();
+                    incrementalMetrics = incrementalManager.getMetrics(codePath);
                 }
             }
         }
@@ -7760,7 +7757,9 @@
                 processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
                         : (r != null) ? r.getProcessClassEnum()
                                       : ServerProtoEnums.ERROR_SOURCE_UNKNOWN,
-                isIncremental, loadingProgress, millisSinceOldestPendingRead
+                incrementalMetrics != null /* isIncremental */, loadingProgress,
+                incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead()
+                        : -1
         );
 
         final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
@@ -7773,7 +7772,8 @@
         }
 
         addErrorToDropBox(
-                eventType, r, processName, null, null, null, null, null, null, crashInfo);
+                eventType, r, processName, null, null, null, null, null, null, crashInfo,
+                new Float(loadingProgress), incrementalMetrics);
 
         mAppErrors.crashApplication(r, crashInfo);
     }
@@ -7955,7 +7955,8 @@
         FrameworkStatsLog.write(FrameworkStatsLog.WTF_OCCURRED, callingUid, tag, processName,
                 callingPid, (r != null) ? r.getProcessClassEnum() : 0);
 
-        addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo);
+        addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo,
+                null, null);
 
         return r;
     }
@@ -7980,7 +7981,7 @@
         for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll();
                 p != null; p = list.poll()) {
             addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null,
-                    p.second);
+                    p.second, null, null);
         }
     }
 
@@ -8069,12 +8070,15 @@
      * @param report in long form describing the error, null if absent
      * @param dataFile text file to include in the report, null if none
      * @param crashInfo giving an application stack trace, null if absent
+     * @param loadingProgress the loading progress of an installed package, range in [0, 1].
+     * @param incrementalMetrics metrics for apps installed on Incremental.
      */
     public void addErrorToDropBox(String eventType,
             ProcessRecord process, String processName, String activityShortComponentName,
             String parentShortComponentName, ProcessRecord parentProcess,
             String subject, final String report, final File dataFile,
-            final ApplicationErrorReport.CrashInfo crashInfo) {
+            final ApplicationErrorReport.CrashInfo crashInfo,
+            @Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics) {
         // NOTE -- this must never acquire the ActivityManagerService lock,
         // otherwise the watchdog may be prevented from resetting the system.
 
@@ -8135,6 +8139,18 @@
         if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) {
             sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n");
         }
+        if (loadingProgress != null) {
+            sb.append("Loading-Progress: ").append(loadingProgress.floatValue()).append("\n");
+        }
+        if (incrementalMetrics != null) {
+            sb.append("Incremental: Yes").append("\n");
+            final long millisSinceOldestPendingRead =
+                    incrementalMetrics.getMillisSinceOldestPendingRead();
+            if (millisSinceOldestPendingRead > 0) {
+                sb.append("Millis-Since-Oldest-Pending-Read: ").append(
+                        millisSinceOldestPendingRead).append("\n");
+            }
+        }
         sb.append("\n");
 
         // Do the rest in a worker thread to avoid blocking the caller on I/O
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 31ea14a..074e8fe 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1627,7 +1627,7 @@
         dropBuilder.append(catSw.toString());
         FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
         mService.addErrorToDropBox("lowmem", null, "system_server", null,
-                null, null, tag.toString(), dropBuilder.toString(), null, null);
+                null, null, tag.toString(), dropBuilder.toString(), null, null, null, null);
         synchronized (mService) {
             long now = SystemClock.uptimeMillis();
             if (mLastMemUsageReportTime < now) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c5f082a..1839e2a 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -382,10 +382,13 @@
     @GuardedBy("mProcLock")
     void compactAppSome(ProcessRecord app) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+        if (!app.mOptRecord.hasPendingCompact()) {
+            app.mOptRecord.setHasPendingCompact(true);
+            mPendingCompactionProcesses.add(app);
+            mCompactionHandler.sendMessage(
+                    mCompactionHandler.obtainMessage(
+                    COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+        }
     }
 
     @GuardedBy("mProcLock")
@@ -396,10 +399,13 @@
                 && app.mState.getCurAdj() >= mCompactThrottleMinOomAdj
                 && app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) {
             app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
-            mPendingCompactionProcesses.add(app);
-            mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+            if (!app.mOptRecord.hasPendingCompact()) {
+                app.mOptRecord.setHasPendingCompact(true);
+                mPendingCompactionProcesses.add(app);
+                mCompactionHandler.sendMessage(
+                        mCompactionHandler.obtainMessage(
+                        COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+            }
         } else {
             if (DEBUG_COMPACTION) {
                 Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -412,10 +418,13 @@
     @GuardedBy("mProcLock")
     void compactAppPersistent(ProcessRecord app) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
+        if (!app.mOptRecord.hasPendingCompact()) {
+            app.mOptRecord.setHasPendingCompact(true);
+            mPendingCompactionProcesses.add(app);
+            mCompactionHandler.sendMessage(
+                    mCompactionHandler.obtainMessage(
                     COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+        }
     }
 
     @GuardedBy("mProcLock")
@@ -427,10 +436,13 @@
     @GuardedBy("mProcLock")
     void compactAppBfgs(ProcessRecord app) {
         app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
+        if (!app.mOptRecord.hasPendingCompact()) {
+            app.mOptRecord.setHasPendingCompact(true);
+            mPendingCompactionProcesses.add(app);
+            mCompactionHandler.sendMessage(
+                    mCompactionHandler.obtainMessage(
                     COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+        }
     }
 
     @GuardedBy("mProcLock")
@@ -954,6 +966,7 @@
                         pendingAction = opt.getReqCompactAction();
                         pid = proc.getPid();
                         name = proc.processName;
+                        opt.setHasPendingCompact(false);
 
                         // don't compact if the process has returned to perceptible
                         // and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 4643610..f4ce723 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -47,6 +47,12 @@
     private int mLastCompactAction;
 
     /**
+     * This process has been scheduled for a memory compaction.
+     */
+    @GuardedBy("mProcLock")
+    private boolean mPendingCompact;
+
+    /**
      * True when the process is frozen.
      */
     @GuardedBy("mProcLock")
@@ -101,6 +107,16 @@
     }
 
     @GuardedBy("mProcLock")
+    boolean hasPendingCompact() {
+        return mPendingCompact;
+    }
+
+    @GuardedBy("mProcLock")
+    void setHasPendingCompact(boolean pendingCompact) {
+        mPendingCompact = pendingCompact;
+    }
+
+    @GuardedBy("mProcLock")
     boolean isFrozen() {
         return mFrozen;
     }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 93f30cc..ab4a2d5 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -300,9 +300,8 @@
         }
 
         // Check if package is still being loaded
-        boolean isIncremental = false;
         float loadingProgress = 1;
-        long millisSinceOldestPendingRead = 0;
+        IncrementalMetrics incrementalMetrics = null;
         final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal();
         if (aInfo != null && aInfo.packageName != null) {
             IncrementalStatesInfo incrementalStatesInfo =
@@ -312,8 +311,7 @@
                 loadingProgress = incrementalStatesInfo.getProgress();
             }
             final String codePath = aInfo.getCodePath();
-            isIncremental = IncrementalManager.isIncrementalPath(codePath);
-            if (isIncremental) {
+            if (IncrementalManager.isIncrementalPath(codePath)) {
                 // Report in the main log that the incremental package is still loading
                 Slog.e(TAG, "App crashed on incremental package " + aInfo.packageName
                         + " which is " + ((int) (loadingProgress * 100)) + "% loaded.");
@@ -322,8 +320,7 @@
                 if (incrementalService != null) {
                     final IncrementalManager incrementalManager = new IncrementalManager(
                             IIncrementalService.Stub.asInterface(incrementalService));
-                    IncrementalMetrics metrics = incrementalManager.getMetrics(codePath);
-                    millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead();
+                    incrementalMetrics = incrementalManager.getMetrics(codePath);
                 }
             }
         }
@@ -345,7 +342,7 @@
             info.append("Parent: ").append(parentShortComponentName).append("\n");
         }
 
-        if (isIncremental) {
+        if (incrementalMetrics != null) {
             // Report in the main log about the incremental package
             info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
         }
@@ -434,12 +431,14 @@
                         : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
                 mApp.getProcessClassEnum(),
                 (mApp.info != null) ? mApp.info.packageName : "",
-                isIncremental, loadingProgress, millisSinceOldestPendingRead);
+                incrementalMetrics != null /* isIncremental */, loadingProgress,
+                incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead()
+                        : -1);
         final ProcessRecord parentPr = parentProcess != null
                 ? (ProcessRecord) parentProcess.mOwner : null;
         mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
                 parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
-                null);
+                null, new Float(loadingProgress), incrementalMetrics);
 
         if (mApp.getWindowProcessController().appNotResponding(info.toString(),
                 () -> {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ec2020f..143a1cf 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -334,13 +334,15 @@
     volatile boolean mBootCompleted;
 
     /**
-     * In this mode, user is always stopped when switched out but locking of user data is
+     * In this mode, user is always stopped when switched out (unless overridden by the
+     * {@code fw.stop_bg_users_on_switch} system property) but locking of user data is
      * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
      * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
     @GuardedBy("mLock")
     private boolean mDelayUserDataLocking;
+
     /**
      * Keep track of last active users for mDelayUserDataLocking.
      * The latest stopped user is placed in front while the least recently stopped user in back.
@@ -406,10 +408,9 @@
         }
     }
 
-    private boolean isDelayUserDataLockingEnabled() {
-        synchronized (mLock) {
-            return mDelayUserDataLocking;
-        }
+    private boolean shouldStopBackgroundUsersOnSwitch() {
+        int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1);
+        return property == -1 ? mDelayUserDataLocking : property == 1;
     }
 
     void finishUserSwitch(UserState uss) {
@@ -1041,6 +1042,11 @@
 
     void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
         final int userId = uss.mHandle.getIdentifier();
+        if (DEBUG_MU) {
+            Slog.i(TAG, "finishUserStopped(%d): allowDelayedLocking=%b", userId,
+                    allowDelayedLocking);
+        }
+
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPED, userId);
         final boolean stopped;
         boolean lockUser = true;
@@ -1153,11 +1159,9 @@
                 Slog.i(TAG, "finishUserStopped, stopping user:" + userId
                         + " lock user:" + userIdToLock);
             } else {
-                Slog.i(TAG, "finishUserStopped, user:" + userId
-                        + ",skip locking");
+                Slog.i(TAG, "finishUserStopped, user:" + userId + ", skip locking");
                 // do not lock
                 userIdToLock = UserHandle.USER_NULL;
-
             }
         }
         return userIdToLock;
@@ -1192,6 +1196,7 @@
     }
 
     private void forceStopUser(@UserIdInt int userId, String reason) {
+        if (DEBUG_MU) Slog.i(TAG, "forceStopUser(%d): %s", userId, reason);
         mInjector.activityManagerForceStopPackage(userId, reason);
         if (mInjector.getUserManager().isPreCreated(userId)) {
             // Don't fire intent for precreated.
@@ -1370,6 +1375,7 @@
 
     private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
             @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
+        if (DEBUG_MU) Slog.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
         EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
 
         final int callingUid = Binder.getCallingUid();
@@ -1790,20 +1796,28 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void stopBackgroundUsersIfEnforced(int oldUserId) {
+    private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) {
         // Never stop system user
         if (oldUserId == UserHandle.USER_SYSTEM) {
             return;
         }
-        // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
-        boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
-                oldUserId) || isDelayUserDataLockingEnabled();
-        if (!disallowRunInBg) {
-            return;
-        }
+        boolean hasRestriction =
+                hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
         synchronized (mLock) {
-            if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
-                    + " and related users");
+            // If running in background is disabled or mStopBackgroundUsersOnSwitch mode,
+            // stop the user.
+            boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch();
+            if (!disallowRunInBg) {
+                if (DEBUG_MU) {
+                    Slog.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related users",
+                            oldUserId);
+                }
+                return;
+            }
+            if (DEBUG_MU) {
+                Slog.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users",
+                        oldUserId);
+            }
             stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
                     null, null);
         }
@@ -1904,7 +1918,7 @@
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
         stopGuestOrEphemeralUserIfBackground(oldUserId);
-        stopBackgroundUsersIfEnforced(oldUserId);
+        stopBackgroundUsersOnSwitchIfEnforced(oldUserId);
     }
 
     private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
@@ -2594,6 +2608,8 @@
             pw.println("  mTargetUserId:" + mTargetUserId);
             pw.println("  mLastActiveUsers:" + mLastActiveUsers);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
+            pw.println("  shouldStopBackgroundUsersOnSwitch:"
+                    + shouldStopBackgroundUsersOnSwitch());
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 0eae661..2ee41ac 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -24,6 +24,7 @@
 import android.app.GameManager.GameMode;
 import android.app.IGameManagerService;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Environment;
@@ -31,6 +32,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -40,6 +43,8 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+
 /**
  * Service to manage game related features.
  *
@@ -58,6 +63,7 @@
     static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
 
     private final Context mContext;
+    private final PackageManager mPackageManager;
     private final Object mLock = new Object();
     private final Handler mHandler;
     @GuardedBy("mLock")
@@ -70,6 +76,13 @@
     GameManagerService(Context context, Looper looper) {
         mContext = context;
         mHandler = new SettingsHandler(looper);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
     }
 
     class SettingsHandler extends Handler {
@@ -171,9 +184,8 @@
     }
 
     private boolean isValidPackageName(String packageName) {
-        final PackageManager pm = mContext.getPackageManager();
         try {
-            return pm.getPackageUid(packageName, 0) == Binder.getCallingUid();
+            return mPackageManager.getPackageUid(packageName, 0) == Binder.getCallingUid();
         } catch (PackageManager.NameNotFoundException e) {
             e.printStackTrace();
             return false;
@@ -208,16 +220,36 @@
     @Override
     public @GameMode int getGameMode(String packageName, int userId)
             throws SecurityException {
-        // TODO(b/178860939): Restrict to games only.
-
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getGameMode",
                 "com.android.server.app.GameManagerService");
 
+        // Restrict to games only.
+        try {
+            final ApplicationInfo applicationInfo = mPackageManager
+                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+            if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
+                Log.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName
+                        + "' which is not categorized as a game: applicationInfo.flags = "
+                        + applicationInfo.flags + ", category = " + applicationInfo.category);
+                return GameManager.GAME_MODE_UNSUPPORTED;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return GameManager.GAME_MODE_UNSUPPORTED;
+        }
+
+        // This function handles two types of queries:
+        // 1.) A normal, non-privileged app querying its own Game Mode.
+        // 2.) A privileged system service querying the Game Mode of another package.
+        // The least privileged case is a normal app performing a query, so check that first and
+        // return a value if the package name is valid. Next, check if the caller has the necessary
+        // permission and return a value. Do this check last, since it can throw an exception.
         if (isValidPackageName(packageName)) {
             return getGameModeFromSettings(packageName, userId);
         }
 
+        // Since the package name doesn't match, check the caller has the necessary permission.
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
         return getGameModeFromSettings(packageName, userId);
     }
@@ -230,15 +262,28 @@
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public void setGameMode(String packageName, @GameMode int gameMode, int userId)
             throws SecurityException {
-        // TODO(b/178860939): Restrict to games only.
-
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
 
-        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), userId, false, true, "setGameMode",
-                "com.android.server.app.GameManagerService");
+        // Restrict to games only.
+        try {
+            final ApplicationInfo applicationInfo = mPackageManager
+                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+            if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
+                Log.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName
+                        + "' which is not categorized as a game: applicationInfo.flags = "
+                        + applicationInfo.flags + ", category = " + applicationInfo.category);
+                return;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return;
+        }
 
         synchronized (mLock) {
+            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, false, true, "setGameMode",
+                    "com.android.server.app.GameManagerService");
+
             if (!mSettings.containsKey(userId)) {
                 return;
             }
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
new file mode 100644
index 0000000..e4c0002
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.compat.Compatibility;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.ShellCommand;
+import android.util.ArraySet;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.wm.CompatModePackages;
+
+import java.io.PrintWriter;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * ShellCommands for GameManagerService.
+ *
+ * Use with {@code adb shell cmd game ...}.
+ */
+public class GameManagerShellCommand extends ShellCommand {
+
+    public GameManagerShellCommand() {}
+
+    private static final ArraySet<Long> DOWNSCALE_CHANGE_IDS = new ArraySet<>(new Long[]{
+            CompatModePackages.DOWNSCALED,
+            CompatModePackages.DOWNSCALE_90,
+            CompatModePackages.DOWNSCALE_80,
+            CompatModePackages.DOWNSCALE_70,
+            CompatModePackages.DOWNSCALE_60,
+            CompatModePackages.DOWNSCALE_50});
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd) {
+                case "downscale":
+                    final String ratio = getNextArgRequired();
+                    final String packageName = getNextArgRequired();
+
+                    final long changeId;
+                    switch (ratio) {
+                        case "0.5":
+                            changeId = CompatModePackages.DOWNSCALE_50;
+                            break;
+                        case "0.6":
+                            changeId = CompatModePackages.DOWNSCALE_60;
+                            break;
+                        case "0.7":
+                            changeId = CompatModePackages.DOWNSCALE_70;
+                            break;
+                        case "0.8":
+                            changeId = CompatModePackages.DOWNSCALE_80;
+                            break;
+                        case "0.9":
+                            changeId = CompatModePackages.DOWNSCALE_90;
+                            break;
+                        case "disable":
+                            changeId = 0;
+                            break;
+                        default:
+                            changeId = -1;
+                            pw.println("Invalid scaling ratio '" + ratio + "'");
+                            break;
+                    }
+                    if (changeId == -1) {
+                        break;
+                    }
+
+                    Set<Long> enabled = new ArraySet<>();
+                    Set<Long> disabled;
+                    if (changeId == 0) {
+                        disabled = DOWNSCALE_CHANGE_IDS;
+                    } else {
+                        enabled.add(CompatModePackages.DOWNSCALED);
+                        enabled.add(changeId);
+                        disabled = DOWNSCALE_CHANGE_IDS.stream()
+                          .filter(it -> it != CompatModePackages.DOWNSCALED && it != changeId)
+                          .collect(Collectors.toSet());
+                    }
+
+                    final PlatformCompat platformCompat = (PlatformCompat)
+                            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+                    final CompatibilityChangeConfig overrides =
+                            new CompatibilityChangeConfig(
+                                new Compatibility.ChangeConfig(enabled, disabled));
+
+                    platformCompat.setOverrides(overrides, packageName);
+                    if (changeId == 0) {
+                        pw.println("Disable downscaling for " + packageName + ".");
+                    } else {
+                        pw.println("Enable downscaling ratio for " + packageName + " to " + ratio);
+                    }
+
+                    break;
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            pw.println("Error: " + e);
+        }
+        return -1;
+    }
+
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Game manager (game) commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  downscale [0.5|0.6|0.7|0.8|0.9|disable] <PACKAGE_NAME>");
+        pw.println("      Force app to run at the specified scaling ratio.");
+    }
+}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index c6824d1..5a99e0e 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.RemoteException;
@@ -69,6 +70,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -91,8 +93,10 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final IPackageManager mIPackageManager;
+    private final PackageManagerInternal mPackageManagerInternal;
     private final IActivityManager mIActivityManager;
     private final UserManager mUserManager;
+
     @GuardedBy("mLock")
     private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
     private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
@@ -101,6 +105,7 @@
     private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
     private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
     private final Injector mInjector;
+    private final Executor mBackgroundExecutor;
 
     @VisibleForTesting
     boolean mIsServiceEnabled;
@@ -123,9 +128,11 @@
         super(injector.getContext());
         mContext = injector.getContext();
         mIPackageManager = injector.getPackageManager();
+        mPackageManagerInternal = injector.getPackageManagerInternal();
         mIActivityManager = injector.getActivityManager();
         mUserManager = injector.getUserManager();
         mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
+        mBackgroundExecutor = injector.getBackgroundExecutor();
         mInjector = injector;
 
         final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
@@ -147,11 +154,13 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_BOOT_COMPLETED) {
-            List<GlobalLevelState> states =
-                    mGlobalLevelHibernationDiskStore.readHibernationStates();
-            synchronized (mLock) {
-                initializeGlobalHibernationStates(states);
-            }
+            mBackgroundExecutor.execute(() -> {
+                List<GlobalLevelState> states =
+                        mGlobalLevelHibernationDiskStore.readHibernationStates();
+                synchronized (mLock) {
+                    initializeGlobalHibernationStates(states);
+                }
+            });
         }
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mIsServiceEnabled = isAppHibernationEnabled();
@@ -170,16 +179,15 @@
      * @return true if package is hibernating for the user
      */
     boolean isHibernatingForUser(String packageName, int userId) {
-        if (!checkHibernationEnabled("isHibernatingForUser")) {
+        String methodName = "isHibernatingForUser";
+        if (!checkHibernationEnabled(methodName)) {
             return false;
         }
         getContext().enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_APP_HIBERNATION,
                 "Caller does not have MANAGE_APP_HIBERNATION permission.");
-        userId = handleIncomingUser(userId, "isHibernating");
-        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
-            Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user "
-                    + userId);
+        userId = handleIncomingUser(userId, methodName);
+        if (!checkUserStatesExist(userId, methodName)) {
             return false;
         }
         synchronized (mLock) {
@@ -210,8 +218,9 @@
         synchronized (mLock) {
             GlobalLevelState state = mGlobalHibernationStates.get(packageName);
             if (state == null) {
-                throw new IllegalArgumentException(
-                        String.format("Package %s is not installed", packageName));
+                // This API can be legitimately called before installation finishes as part of
+                // dex optimization, so we just return false here.
+                return false;
             }
             return state.hibernated;
         }
@@ -225,16 +234,15 @@
      * @param isHibernating new hibernation state
      */
     void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
-        if (!checkHibernationEnabled("setHibernatingForUser")) {
+        String methodName = "setHibernatingForUser";
+        if (!checkHibernationEnabled(methodName)) {
             return;
         }
         getContext().enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_APP_HIBERNATION,
                 "Caller does not have MANAGE_APP_HIBERNATION permission.");
-        userId = handleIncomingUser(userId, "setHibernating");
-        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
-            Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user "
-                    + userId);
+        userId = handleIncomingUser(userId, methodName);
+        if (!checkUserStatesExist(userId, methodName)) {
             return;
         }
         synchronized (mLock) {
@@ -298,16 +306,15 @@
      */
     @NonNull List<String> getHibernatingPackagesForUser(int userId) {
         ArrayList<String> hibernatingPackages = new ArrayList<>();
-        if (!checkHibernationEnabled("getHibernatingPackagesForUser")) {
+        String methodName = "getHibernatingPackagesForUser";
+        if (!checkHibernationEnabled(methodName)) {
             return hibernatingPackages;
         }
         getContext().enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_APP_HIBERNATION,
                 "Caller does not have MANAGE_APP_HIBERNATION permission.");
-        userId = handleIncomingUser(userId, "getHibernatingPackagesForUser");
-        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
-            Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user "
-                    + userId);
+        userId = handleIncomingUser(userId, methodName);
+        if (!checkUserStatesExist(userId, methodName)) {
             return hibernatingPackages;
         }
         synchronized (mLock) {
@@ -364,7 +371,7 @@
     @GuardedBy("mLock")
     private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
-        // TODO(175830194): Delete vdex/odex when DexManager API is built out
+        mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName);
         state.hibernated = true;
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
@@ -468,10 +475,15 @@
         HibernationStateDiskStore<UserLevelState> diskStore =
                 mInjector.getUserLevelDiskStore(userId);
         mUserDiskStores.put(userId, diskStore);
-        List<UserLevelState> storedStates = diskStore.readHibernationStates();
-        synchronized (mLock) {
-            initializeUserHibernationStates(userId, storedStates);
-        }
+        mBackgroundExecutor.execute(() -> {
+            List<UserLevelState> storedStates = diskStore.readHibernationStates();
+            synchronized (mLock) {
+                // Ensure user hasn't stopped in the time to execute.
+                if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
+                    initializeUserHibernationStates(userId, storedStates);
+                }
+            }
+        });
     }
 
     @Override
@@ -541,6 +553,20 @@
         }
     }
 
+    private boolean checkUserStatesExist(int userId, String methodName) {
+        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+            Slog.e(TAG, String.format(
+                    "Attempt to call %s on stopped or nonexistent user %d", methodName, userId));
+            return false;
+        }
+        if (!mUserStates.contains(userId)) {
+            Slog.w(TAG, String.format(
+                    "Attempt to call %s before states have been read from disk", methodName));
+            return false;
+        }
+        return true;
+    }
+
     private boolean checkHibernationEnabled(String methodName) {
         if (!mIsServiceEnabled) {
             Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName));
@@ -707,10 +733,14 @@
 
         IPackageManager getPackageManager();
 
+        PackageManagerInternal getPackageManagerInternal();
+
         IActivityManager getActivityManager();
 
         UserManager getUserManager();
 
+        Executor getBackgroundExecutor();
+
         HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
 
         HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
@@ -739,6 +769,11 @@
         }
 
         @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        @Override
         public IActivityManager getActivityManager() {
             return ActivityManager.getService();
         }
@@ -749,6 +784,11 @@
         }
 
         @Override
+        public Executor getBackgroundExecutor() {
+            return mScheduledExecutorService;
+        }
+
+        @Override
         public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
             File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);
             return new HibernationStateDiskStore<>(
diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
index c83659d..24cf433 100644
--- a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
+++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
@@ -109,6 +109,7 @@
      * @return the parsed list of hibernation states, null if file does not exist
      */
     @Nullable
+    @WorkerThread
     List<T> readHibernationStates() {
         synchronized (this) {
             if (!mHibernationFile.exists()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 109ffe3..7bc7105 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1922,6 +1922,7 @@
         synchronized (this) {
             if (mWriteScheduled) {
                 mWriteScheduled = false;
+                mFastWriteScheduled = false;
                 mHandler.removeCallbacks(mWriteRunner);
                 doWrite = true;
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 8b9be83..3d69326 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -25,18 +25,27 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
 
-public abstract class StartUserClient<T>  extends HalClientMonitor<T> {
+/**
+ * Abstract class for starting a new user.
+ * @param <T> Interface to request a new user.
+ * @param <U> Newly created user object.
+ */
+public abstract class StartUserClient<T, U>  extends HalClientMonitor<T> {
 
-    public interface UserStartedCallback {
-        void onUserStarted(int newUserId);
+    /**
+     * Invoked when the new user is started.
+     * @param <U> New user object.
+     */
+    public interface UserStartedCallback<U> {
+        void onUserStarted(int newUserId, U newUser);
     }
 
     @NonNull @VisibleForTesting
-    protected final UserStartedCallback mUserStartedCallback;
+    protected final UserStartedCallback<U> mUserStartedCallback;
 
     public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
-            @NonNull UserStartedCallback callback) {
+            @NonNull UserStartedCallback<U> callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
                 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
                 BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 62cd673..1f6e1e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -25,6 +25,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
 
+/**
+ * Abstract class for stopping a user.
+ * @param <T> Interface for stopping the user.
+ */
 public abstract class StopUserClient<T> extends HalClientMonitor<T> {
 
     public interface UserStoppedCallback {
@@ -32,7 +36,12 @@
     }
 
     @NonNull @VisibleForTesting
-    protected final UserStoppedCallback mUserStoppedCallback;
+    private final UserStoppedCallback mUserStoppedCallback;
+
+    public void onUserStopped() {
+        mUserStoppedCallback.onUserStopped();
+        getCallback().onClientFinished(this, true /* success */);
+    }
 
     public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index c0ea2b3..f015a80 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -45,13 +45,15 @@
 
     public interface UserSwitchCallback {
         @NonNull StopUserClient<?> getStopUserClient(int userId);
-        @NonNull StartUserClient<?> getStartUserClient(int newUserId);
+        @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
     }
 
     @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
     @NonNull private final UserSwitchCallback mUserSwitchCallback;
     @NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
 
+    @Nullable private StopUserClient<?> mStopUserClient;
+
     @VisibleForTesting
     class ClientFinishedCallback implements BaseClientMonitor.Callback {
         @Override
@@ -108,16 +110,31 @@
         if (nextUserId == currentUserId) {
             super.startNextOperationIfIdle();
         } else if (currentUserId == UserHandle.USER_NULL) {
-            Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
             final BaseClientMonitor startClient =
                     mUserSwitchCallback.getStartUserClient(nextUserId);
+            Slog.d(getTag(), "[Starting User] " + startClient);
             startClient.start(mClientFinishedCallback);
         } else {
-            final BaseClientMonitor stopClient = mUserSwitchCallback
-                    .getStopUserClient(currentUserId);
-            Slog.d(getTag(), "User switch required, current: " + currentUserId
-                    + ", next: " + nextUserId + ". " + stopClient);
-            stopClient.start(mClientFinishedCallback);
+            if (mStopUserClient != null) {
+                Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
+            } else {
+                mStopUserClient = mUserSwitchCallback
+                        .getStopUserClient(currentUserId);
+                Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+                        + ", next: " + nextUserId + ". " + mStopUserClient);
+                mStopUserClient.start(mClientFinishedCallback);
+            }
         }
     }
+
+    public void onUserStopped() {
+        if (mStopUserClient == null) {
+            Slog.e(getTag(), "Unexpected onUserStopped");
+            return;
+        }
+
+        Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
+        mStopUserClient.onUserStopped();
+        mStopUserClient = null;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 9f5dc69..d10fd4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -502,9 +502,6 @@
                 return false;
             }
 
-            final boolean enrolled = provider.getEnrolledFaces(sensorId, userId).size() > 0;
-            Slog.d(TAG, "hasEnrolledFaces, sensor: " + sensorId + ", enrolled: " + enrolled);
-
             return provider.getEnrolledFaces(sensorId, userId).size() > 0;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index fdc3bb1..a9be8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -78,7 +78,6 @@
     @NonNull private final String mHalInstanceName;
     @NonNull @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final HalClientMonitor.LazyDaemon<IFace> mLazyDaemon;
     @NonNull private final Handler mHandler;
     @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
     @NonNull private final UsageStats mUsageStats;
@@ -126,7 +125,6 @@
         mContext = context;
         mHalInstanceName = halInstanceName;
         mSensors = new SparseArray<>();
-        mLazyDaemon = this::getHalInstance;
         mHandler = new Handler(Looper.getMainLooper());
         mUsageStats = new UsageStats(context);
         mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -163,7 +161,8 @@
     }
 
     @Nullable
-    private synchronized IFace getHalInstance() {
+    @VisibleForTesting
+    synchronized IFace getHalInstance() {
         if (mTestHalEnabled) {
             return new TestHal();
         }
@@ -214,22 +213,6 @@
         mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
     }
 
-    private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
-            int userId) throws RemoteException {
-        // Note that per IFace createSession contract, this method will block until all
-        // existing operations are canceled/finished. However, also note that this is fine, since
-        // this method "withoutHandler" means it should only ever be invoked from the worker thread,
-        // so callers will never be blocked.
-        mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
-        if (FaceUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
-            Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
-                    + ", user: " + userId);
-            scheduleInvalidationRequest(sensorId, userId);
-        }
-    }
-
-
     private void scheduleLoadAuthenticatorIds(int sensorId) {
         for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
             scheduleLoadAuthenticatorIdsForUser(sensorId, user.id);
@@ -238,32 +221,16 @@
 
     private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
-                return;
-            }
+            final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
+                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext.getOpPackageName(), sensorId,
+                    mSensors.get(sensorId).getAuthenticatorIds());
 
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
-                        mContext, mSensors.get(sensorId).getLazySession(), userId,
-                        mContext.getOpPackageName(), sensorId,
-                        mSensors.get(sensorId).getAuthenticatorIds());
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
-                        + ", sensorId: " + sensorId
-                        + ", userId: " + userId, e);
-            }
+            scheduleForSensor(sensorId, client);
         });
     }
 
-    private void scheduleInvalidationRequest(int sensorId, int userId) {
+    void scheduleInvalidationRequest(int sensorId, int userId) {
         mHandler.post(() -> {
             final InvalidationRequesterClient<Face> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
@@ -303,25 +270,10 @@
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
-                        + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), userId, sensorId,
-                        mSensors.get(sensorId).getAuthenticatorIds(), callback);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception", e);
-            }
+            final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                    mSensors.get(sensorId).getAuthenticatorIds(), callback);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -344,25 +296,10 @@
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, String opPackageName) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
-            }
+            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -370,25 +307,10 @@
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
-                        challenge);
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
-            }
+            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
+                    challenge);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -398,41 +320,24 @@
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
             @Nullable NativeHandle previewSurface, boolean debugConsent) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final int maxTemplatesPerUser = mSensors.get(
-                        sensorId).getSensorProperties().maxEnrollmentsPerUser;
-                final FaceEnrollClient client = new FaceEnrollClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                        opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
-                        ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
-                        debugConsent);
-                scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
-                    @Override
-                    public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                            boolean success) {
-                        if (success) {
-                            scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                            scheduleInvalidationRequest(sensorId, userId);
-                        }
+            final int maxTemplatesPerUser = mSensors.get(
+                    sensorId).getSensorProperties().maxEnrollmentsPerUser;
+            final FaceEnrollClient client = new FaceEnrollClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+                    opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
+                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
+                    debugConsent);
+            scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                        scheduleInvalidationRequest(sensorId, userId);
                     }
-                });
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling enroll", e);
-            }
+                }
+            });
         });
     }
 
@@ -447,31 +352,14 @@
             @NonNull String opPackageName, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-                final FaceAuthenticationClient client = new FaceAuthenticationClient(
-                        mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                        operationId, restricted, opPackageName, cookie,
-                        false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
-                        mUsageStats, mSensors.get(sensorId).getLockoutCache(),
-                        allowBackgroundAuthentication);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
-            }
+            final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            final FaceAuthenticationClient client = new FaceAuthenticationClient(
+                    mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    operationId, restricted, opPackageName, cookie,
+                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    mUsageStats, mSensors.get(sensorId).getLockoutCache(),
+                    allowBackgroundAuthentication);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -503,56 +391,24 @@
     private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceRemovalClient client = new FaceRemovalClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), faceIds, userId,
-                        opPackageName, FaceUtils.getInstance(sensorId), sensorId,
-                        mSensors.get(sensorId).getAuthenticatorIds());
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling remove", e);
-            }
+            final FaceRemovalClient client = new FaceRemovalClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), faceIds, userId,
+                    opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+                    mSensors.get(sensorId).getAuthenticatorIds());
+            scheduleForSensor(sensorId, client);
         });
     }
 
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
-                return;
-            }
+            final FaceResetLockoutClient client = new FaceResetLockoutClient(
+                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
 
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FaceResetLockoutClient client = new FaceResetLockoutClient(
-                        mContext, mSensors.get(sensorId).getLazySession(), userId,
-                        mContext.getOpPackageName(), sensorId, hardwareAuthToken,
-                        mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
-            }
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -580,29 +436,14 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
-            final IFace daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
-                final FaceInternalCleanupClient client =
-                        new FaceInternalCleanupClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), userId,
-                                mContext.getOpPackageName(), sensorId, enrolledList,
-                                FaceUtils.getInstance(sensorId),
-                                mSensors.get(sensorId).getAuthenticatorIds());
-
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
-            }
+            final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
+            final FaceInternalCleanupClient client =
+                    new FaceInternalCleanupClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), userId,
+                            mContext.getOpPackageName(), sensorId, enrolledList,
+                            FaceUtils.getInstance(sensorId),
+                            mSensors.get(sensorId).getAuthenticatorIds());
+            scheduleForSensor(sensorId, client);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
new file mode 100644
index 0000000..c364dbb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
+    private static final String TAG = "FaceStartUserClient";
+
+    @NonNull private final ISessionCallback mSessionCallback;
+
+    public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon,
+            @Nullable IBinder token, int userId, int sensorId,
+            @NonNull ISessionCallback sessionCallback,
+            @NonNull UserStartedCallback<ISession> callback) {
+        super(context, lazyDaemon, token, userId, sensorId, callback);
+        mSessionCallback = sessionCallback;
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+                    getTargetUserId(), mSessionCallback);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+            getCallback().onClientFinished(this, true /* success */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            getCallback().onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
new file mode 100644
index 0000000..8d3853b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FaceStopUserClient extends StopUserClient<ISession> {
+    private static final String TAG = "FaceStopUserClient";
+
+    public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+            @Nullable IBinder token, int userId, int sensorId,
+            @NonNull UserStoppedCallback callback) {
+        super(context, lazyDaemon, token, userId, sensorId, callback);
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().close(mSequentialId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            getCallback().onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3eb4759..768f464 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -33,8 +33,11 @@
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,9 @@
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -70,9 +76,10 @@
     @NonNull private final String mTag;
     @NonNull private final FaceProvider mProvider;
     @NonNull private final Context mContext;
+    @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final UserAwareBiometricScheduler mScheduler;
     @NonNull private final LockoutCache mLockoutCache;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
@@ -112,14 +119,14 @@
         @NonNull
         private final String mTag;
         @NonNull
-        private final BiometricScheduler mScheduler;
+        private final UserAwareBiometricScheduler mScheduler;
         private final int mSensorId;
         private final int mUserId;
         @NonNull
         private final Callback mCallback;
 
         HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
                 @NonNull Callback callback) {
             mContext = context;
             mHandler = handler;
@@ -426,9 +433,7 @@
 
         @Override
         public void onSessionClosed() {
-            mHandler.post(() -> {
-              // TODO: implement this.
-            });
+            mHandler.post(mScheduler::onUserStopped);
         }
     }
 
@@ -437,9 +442,53 @@
         mTag = tag;
         mProvider = provider;
         mContext = context;
+        mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
+        mScheduler = new UserAwareBiometricScheduler(tag, null /* gestureAvailabilityDispatcher */,
+                () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<?> getStopUserClient(int userId) {
+                        return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
+                                mSensorProperties.sensorId, () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                        final HalSessionCallback.Callback callback = () -> {
+                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                            mCurrentSession = null;
+                        };
+
+                        final int sensorId = mSensorProperties.sensorId;
+
+                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
+                                mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                                (userIdStarted, newSession) -> {
+                                    mCurrentSession = new Session(mTag, newSession, userIdStarted,
+                                            resultController);
+                                    if (FaceUtils.getLegacyInstance(sensorId)
+                                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                                        Slog.w(mTag,
+                                                "Scheduling unfinished invalidation request for "
+                                                        + "sensor: "
+                                                        + sensorId
+                                                        + ", user: " + userIdStarted);
+                                        provider.scheduleInvalidationRequest(sensorId,
+                                                userIdStarted);
+                                    }
+                                };
+
+                        return new FaceStartUserClient(mContext, provider::getHalInstance,
+                                mToken, newUserId, mSensorProperties.sensorId,
+                                resultController, userStartedCallback);
+                    }
+                });
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -453,11 +502,6 @@
         return mSensorProperties;
     }
 
-    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
-    boolean hasSessionForUser(int userId) {
-        return mCurrentSession != null && mCurrentSession.mUserId == userId;
-    }
-
     @Nullable Session getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
             return mCurrentSession;
@@ -471,20 +515,6 @@
                 mProvider, this);
     }
 
-    void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
-            throws RemoteException {
-
-        final HalSessionCallback.Callback callback = () -> {
-            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-            mCurrentSession = null;
-        };
-        final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
-                mTag, mScheduler, sensorId, userId, callback);
-
-        final ISession newSession = daemon.createSession(sensorId, userId, resultController);
-        mCurrentSession = new Session(mTag, newSession, userId, resultController);
-    }
-
     @NonNull BiometricScheduler getScheduler() {
         return mScheduler;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 4ca85d0..36327bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -135,7 +135,8 @@
 
             @Override
             public void close(int cookie) throws RemoteException {
-                cb.onStateChanged(cookie, SessionState.CLOSED);
+                Slog.w(TAG, "close, cookie: " + cookie);
+                cb.onSessionClosed();
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index bcca69b..972071c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -82,7 +82,6 @@
     @NonNull private final String mHalInstanceName;
     @NonNull @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final HalClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
     @NonNull private final Handler mHandler;
     @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
     @NonNull private final ActivityTaskManager mActivityTaskManager;
@@ -131,7 +130,6 @@
         mContext = context;
         mHalInstanceName = halInstanceName;
         mSensors = new SparseArray<>();
-        mLazyDaemon = this::getHalInstance;
         mHandler = new Handler(Looper.getMainLooper());
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -169,7 +167,8 @@
     }
 
     @Nullable
-    private synchronized IFingerprint getHalInstance() {
+    @VisibleForTesting
+    synchronized IFingerprint getHalInstance() {
         if (mTestHalEnabled) {
             // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
             // the test HAL for all sensors under that HAL. This can be updated in the future if
@@ -224,21 +223,6 @@
         mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
     }
 
-    private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
-            int userId) throws RemoteException {
-        // Note that per IFingerprint createSession contract, this method will block until all
-        // existing operations are canceled/finished. However, also note that this is fine, since
-        // this method "withoutHandler" means it should only ever be invoked from the worker thread,
-        // so callers will never be blocked.
-        mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
-        if (FingerprintUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
-            Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
-                    + ", user: " + userId);
-            scheduleInvalidationRequest(sensorId, userId);
-        }
-    }
-
     @Override
     public boolean containsSensor(int sensorId) {
         return mSensors.contains(sensorId);
@@ -275,32 +259,16 @@
 
     private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintGetAuthenticatorIdClient client =
-                        new FingerprintGetAuthenticatorIdClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), userId,
-                                mContext.getOpPackageName(), sensorId,
-                                mSensors.get(sensorId).getAuthenticatorIds());
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
-                        + ", sensorId: " + sensorId
-                        + ", userId: " + userId, e);
-            }
+            final FingerprintGetAuthenticatorIdClient client =
+                    new FingerprintGetAuthenticatorIdClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), userId,
+                            mContext.getOpPackageName(), sensorId,
+                            mSensors.get(sensorId).getAuthenticatorIds());
+            scheduleForSensor(sensorId, client);
         });
     }
 
-    private void scheduleInvalidationRequest(int sensorId, int userId) {
+    void scheduleInvalidationRequest(int sensorId, int userId) {
         mHandler.post(() -> {
             final InvalidationRequesterClient<Fingerprint> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
@@ -312,25 +280,11 @@
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
-                        mContext, mSensors.get(sensorId).getLazySession(), userId,
-                        mContext.getOpPackageName(), sensorId, hardwareAuthToken,
-                        mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
-            }
+            final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
+                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                    mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -338,26 +292,12 @@
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, String opPackageName) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintGenerateChallengeClient client =
-                        new FingerprintGenerateChallengeClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), token,
-                                new ClientMonitorCallbackConverter(receiver), opPackageName,
-                                sensorId);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
-            }
+            final FingerprintGenerateChallengeClient client =
+                    new FingerprintGenerateChallengeClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), token,
+                            new ClientMonitorCallbackConverter(receiver), opPackageName,
+                            sensorId);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -365,25 +305,11 @@
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintRevokeChallengeClient client =
-                        new FingerprintRevokeChallengeClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), token,
-                                opPackageName, sensorId, challenge);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
-            }
+            final FingerprintRevokeChallengeClient client =
+                    new FingerprintRevokeChallengeClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), token,
+                            opPackageName, sensorId, challenge);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -392,40 +318,23 @@
             int userId, @NonNull IFingerprintServiceReceiver receiver,
             @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
-                        .maxEnrollmentsPerUser;
-                final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                        opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                        mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
-                scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
-                    @Override
-                    public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                            boolean success) {
-                        if (success) {
-                            scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
-                            scheduleInvalidationRequest(sensorId, userId);
-                        }
+            final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+                    .maxEnrollmentsPerUser;
+            final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
+            scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+                        scheduleInvalidationRequest(sensorId, userId);
                     }
-                });
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling enroll", e);
-            }
+                }
+            });
         });
     }
 
@@ -439,29 +348,12 @@
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
             int statsClient) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during finger detect, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-                final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                        opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
-                        statsClient);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
-            }
+            final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
+                    statsClient);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -471,31 +363,14 @@
             @NonNull String opPackageName, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-                final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                        mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                        operationId, restricted, opPackageName, cookie,
-                        false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
-                        mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
-                        mUdfpsOverlayController, allowBackgroundAuthentication);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
-            }
+            final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+            final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+                    mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    operationId, restricted, opPackageName, cookie,
+                    false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                    mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+                    mUdfpsOverlayController, allowBackgroundAuthentication);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -535,29 +410,12 @@
             int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver,
             @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
-                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
-                // this operation. We should not send the callback yet, since the scheduler may
-                // be processing something else.
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                        mSensors.get(sensorId).getLazySession(), token,
-                        new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
-                        opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                        mSensors.get(sensorId).getAuthenticatorIds());
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling remove", e);
-            }
+            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
+                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+                    mSensors.get(sensorId).getAuthenticatorIds());
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -565,28 +423,14 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable BaseClientMonitor.Callback callback) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
-                final FingerprintInternalCleanupClient client =
-                        new FingerprintInternalCleanupClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), userId,
-                                mContext.getOpPackageName(), sensorId, enrolledList,
-                                FingerprintUtils.getInstance(sensorId),
-                                mSensors.get(sensorId).getAuthenticatorIds());
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
-            }
+            final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
+            final FingerprintInternalCleanupClient client =
+                    new FingerprintInternalCleanupClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), userId,
+                            mContext.getOpPackageName(), sensorId, enrolledList,
+                            FingerprintUtils.getInstance(sensorId),
+                            mSensors.get(sensorId).getAuthenticatorIds());
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -611,26 +455,11 @@
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
-            final IFingerprint daemon = getHalInstance();
-            if (daemon == null) {
-                Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
-                        + sensorId);
-                return;
-            }
-
-            try {
-                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    createNewSessionWithoutHandler(daemon, sensorId, userId);
-                }
-
-                final FingerprintInvalidationClient client =
-                        new FingerprintInvalidationClient(mContext,
-                                mSensors.get(sensorId).getLazySession(), userId, sensorId,
-                                mSensors.get(sensorId).getAuthenticatorIds(), callback);
-                scheduleForSensor(sensorId, client);
-            } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception", e);
-            }
+            final FingerprintInvalidationClient client =
+                    new FingerprintInvalidationClient(mContext,
+                            mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                            mSensors.get(sensorId).getAuthenticatorIds(), callback);
+            scheduleForSensor(sensorId, client);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
new file mode 100644
index 0000000..2d40c91
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
+    private static final String TAG = "FingerprintStartUserClient";
+
+    @NonNull private final ISessionCallback mSessionCallback;
+
+    public FingerprintStartUserClient(@NonNull Context context,
+            @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+            @Nullable IBinder token, int userId, int sensorId,
+            @NonNull ISessionCallback sessionCallback,
+            @NonNull UserStartedCallback<ISession> callback) {
+        super(context, lazyDaemon, token, userId, sensorId, callback);
+        mSessionCallback = sessionCallback;
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+                    getTargetUserId(), mSessionCallback);
+            mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+            getCallback().onClientFinished(this, true /* success */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            getCallback().onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
new file mode 100644
index 0000000..ba81357
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FingerprintStopUserClient extends StopUserClient<ISession> {
+    private static final String TAG = "FingerprintStopUserClient";
+
+    public FingerprintStopUserClient(@NonNull Context context,
+            @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+            int sensorId, @NonNull UserStoppedCallback callback) {
+        super(context, lazyDaemon, token, userId, sensorId, callback);
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().close(mSequentialId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            getCallback().onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public void unableToStart() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d843bc9..cd12d02 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -24,15 +24,17 @@
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.Error;
-import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.ISessionCallback;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -53,6 +55,9 @@
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
@@ -72,9 +77,10 @@
     @NonNull private final String mTag;
     @NonNull private final FingerprintProvider mProvider;
     @NonNull private final Context mContext;
+    @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final UserAwareBiometricScheduler mScheduler;
     @NonNull private final LockoutCache mLockoutCache;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
@@ -112,13 +118,13 @@
         @NonNull private final Context mContext;
         @NonNull private final Handler mHandler;
         @NonNull private final String mTag;
-        @NonNull private final BiometricScheduler mScheduler;
+        @NonNull private final UserAwareBiometricScheduler mScheduler;
         private final int mSensorId;
         private final int mUserId;
         @NonNull private final Callback mCallback;
 
         HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
                 @NonNull Callback callback) {
             mContext = context;
             mHandler = handler;
@@ -406,9 +412,7 @@
 
         @Override
         public void onSessionClosed() {
-            mHandler.post(() -> {
-              // TODO: implement this.
-            });
+            mHandler.post(mScheduler::onUserStopped);
         }
     }
 
@@ -418,9 +422,53 @@
         mTag = tag;
         mProvider = provider;
         mContext = context;
+        mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+        mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
+                () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<?> getStopUserClient(int userId) {
+                        return new FingerprintStopUserClient(mContext, mLazySession, mToken,
+                                userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                        final HalSessionCallback.Callback callback = () -> {
+                            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+                            mCurrentSession = null;
+                        };
+
+                        final int sensorId = mSensorProperties.sensorId;
+
+                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
+                                mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                                (userIdStarted, newSession) -> {
+                                    mCurrentSession = new Session(mTag,
+                                            newSession, userIdStarted, resultController);
+                                    if (FingerprintUtils.getInstance(sensorId)
+                                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                                        Slog.w(mTag,
+                                                "Scheduling unfinished invalidation request for "
+                                                        + "sensor: "
+                                                        + sensorId
+                                                        + ", user: " + userIdStarted);
+                                        provider.scheduleInvalidationRequest(sensorId,
+                                                userIdStarted);
+                                    }
+                                };
+
+                        return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+                                mToken, newUserId, mSensorProperties.sensorId,
+                                resultController, userStartedCallback);
+                    }
+                });
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -434,11 +482,6 @@
         return mSensorProperties;
     }
 
-    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
-    boolean hasSessionForUser(int userId) {
-        return mCurrentSession != null && mCurrentSession.mUserId == userId;
-    }
-
     @Nullable Session getSessionForUser(int userId) {
         if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
             return mCurrentSession;
@@ -452,20 +495,6 @@
                 mProvider, this);
     }
 
-    void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
-            throws RemoteException {
-
-        final HalSessionCallback.Callback callback = () -> {
-            Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-            mCurrentSession = null;
-        };
-        final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
-                mTag, mScheduler, sensorId, userId, callback);
-
-        final ISession newSession = daemon.createSession(sensorId, userId, resultController);
-        mCurrentSession = new Session(mTag, newSession, userId, resultController);
-    }
-
     @NonNull BiometricScheduler getScheduler() {
         return mScheduler;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 0b7f3ab..31fc068 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -121,7 +121,8 @@
 
             @Override
             public void close(int cookie) throws RemoteException {
-                cb.onStateChanged(cookie, SessionState.CLOSED);
+                Slog.w(TAG, "close, cookie: " + cookie);
+                cb.onSessionClosed();
             }
 
             @Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 781bad7..41bc0b9 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -121,6 +121,17 @@
         return false;
     }
 
+    private void closePipe() {
+        try {
+            final RandomAccessFile pipe = mPipe;
+            mPipe = null;
+            if (pipe != null) {
+                pipe.close();
+            }
+        } catch (IOException ignore) {
+        }
+    }
+
     public HostClipboardMonitor(HostClipboardCallback cb) {
         mHostClipboardCallback = cb;
     }
@@ -142,10 +153,7 @@
                 mHostClipboardCallback.onHostClipboardUpdated(
                     new String(receivedData));
             } catch (IOException e) {
-                try {
-                    mPipe.close();
-                } catch (IOException ee) {}
-                mPipe = null;
+                closePipe();
             } catch (InterruptedException e) {}
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java
index 028cfee..9326d69 100644
--- a/services/core/java/com/android/server/connectivity/FullScore.java
+++ b/services/core/java/com/android/server/connectivity/FullScore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 
@@ -116,6 +117,33 @@
     }
 
     /**
+     * Given a score supplied by the NetworkAgent, produce a prospective score for an offer.
+     *
+     * NetworkOffers have score filters that are compared to the scores of actual networks
+     * to see if they could possibly beat the current satisfier. Some things the agent can't
+     * know in advance ; a good example is the validation bit – some networks will validate,
+     * others won't. For comparison purposes, assume the best, so all possibly beneficial
+     * networks will be brought up.
+     *
+     * @param score the score supplied by the agent for this offer
+     * @param caps the capabilities supplied by the agent for this offer
+     * @return a FullScore appropriate for comparing to actual network's scores.
+     */
+    public static FullScore makeProspectiveScore(@NonNull final NetworkScore score,
+            @NonNull final NetworkCapabilities caps) {
+        // If the network offers Internet access, it may validate.
+        final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET);
+        // VPN transports are known in advance.
+        final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
+        // The network hasn't been chosen by the user (yet, at least).
+        final boolean everUserSelected = false;
+        // Don't assume the user will accept unvalidated connectivity.
+        final boolean acceptUnvalidated = false;
+        return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected,
+                acceptUnvalidated);
+    }
+
+    /**
      * Return a new score given updated caps and config.
      *
      * @param caps the NetworkCapabilities of the network
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f3d2012..6ea84ce 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -18,12 +18,14 @@
 
 import static android.util.TimeUtils.NANOS_PER_MS;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
 import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.metrics.ConnectStats;
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
@@ -98,6 +100,7 @@
     private final TokenBucket mConnectTb =
             new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
 
+    final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback();
 
     /**
      * There are only 3 possible callbacks.
@@ -158,6 +161,9 @@
     public NetdEventListenerService(ConnectivityManager cm) {
         // We are started when boot is complete, so ConnectivityService should already be running.
         mCm = cm;
+        // Clear all capabilities to listen all networks.
+        mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(),
+                mCallback);
     }
 
     private static long projectSnapshotTime(long timeMs) {
@@ -389,18 +395,13 @@
     }
 
     private long getTransports(int netId) {
-        // TODO: directly query ConnectivityService instead of going through Binder interface.
-        NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
+        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
         if (nc == null) {
             return 0;
         }
         return BitUtils.packBits(nc.getTransportTypes());
     }
 
-    private static void maybeLog(String s, Object... args) {
-        if (DBG) Log.d(TAG, String.format(s, args));
-    }
-
     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
     static class NetworkMetricsSnapshot {
 
@@ -428,4 +429,29 @@
             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
         }
     }
+
+    private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            synchronized (mCapabilities) {
+                mCapabilities.put(network.getNetId(), nc);
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            synchronized (mCapabilities) {
+                mCapabilities.remove(network.getNetId());
+            }
+        }
+
+        @Nullable
+        public NetworkCapabilities getNetworkCapabilities(int netId) {
+            synchronized (mCapabilities) {
+                return mCapabilities.get(netId);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java
new file mode 100644
index 0000000..548db6b
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java
@@ -0,0 +1,93 @@
+/*
+ * 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.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.INetworkOfferCallback;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import java.util.Objects;
+
+
+/**
+ * Represents an offer made by a NetworkProvider to create a network if a need arises.
+ *
+ * This class contains the prospective score and capabilities of the network. The provider
+ * is not obligated to caps able to create a network satisfying this, nor to build a network
+ * with the exact score and/or capabilities passed ; after all, not all providers know in
+ * advance what a network will look like after it's connected. Instead, this is meant as a
+ * filter to limit requests sent to the provider by connectivity to those that this offer stands
+ * a chance to fulfill.
+ *
+ * @see NetworkProvider#offerNetwork.
+ *
+ * @hide
+ */
+public class NetworkOffer {
+    @NonNull public final FullScore score;
+    @NonNull public final NetworkCapabilities caps;
+    @NonNull public final INetworkOfferCallback callback;
+    @NonNull public final int providerId;
+
+    private static NetworkCapabilities emptyCaps() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        return nc;
+    }
+
+    // Ideally the caps argument would be non-null, but null has historically meant no filter
+    // and telephony passes null. Keep backward compatibility.
+    public NetworkOffer(@NonNull final FullScore score,
+            @Nullable final NetworkCapabilities caps,
+            @NonNull final INetworkOfferCallback callback,
+            @NonNull final int providerId) {
+        this.score = Objects.requireNonNull(score);
+        this.caps = null != caps ? caps : emptyCaps();
+        this.callback = Objects.requireNonNull(callback);
+        this.providerId = providerId;
+    }
+
+    /**
+     * Migrate from, and take over, a previous offer.
+     *
+     * When an updated offer is sent from a provider, call this method on the new offer, passing
+     * the old one, to take over the state.
+     *
+     * @param previousOffer
+     */
+    public void migrateFrom(@NonNull final NetworkOffer previousOffer) {
+        if (!callback.equals(previousOffer.callback)) {
+            throw new IllegalArgumentException("Can only migrate from a previous version of"
+                    + " the same offer");
+        }
+    }
+
+    /**
+     * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities.
+     * @param request The request to test against.
+     * @return Whether this offer can satisfy the request.
+     */
+    public final boolean canSatisfy(@NonNull final NetworkRequest request) {
+        return request.networkCapabilities.satisfiedByNetworkCapabilities(caps);
+    }
+
+    @Override
+    public String toString() {
+        return "NetworkOffer [ Score " + score + " ]";
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 488677a..3711679 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -271,6 +271,13 @@
         return mApps.containsKey(uid);
     }
 
+    /**
+     * Returns whether the given uid has permission to use restricted networks.
+     */
+    public synchronized boolean hasRestrictedNetworksPermission(int uid) {
+        return Boolean.TRUE.equals(mApps.get(uid));
+    }
+
     private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
         List<Integer> network = new ArrayList<>();
         List<Integer> system = new ArrayList<>();
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 70401b4..a070f27 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1133,6 +1133,8 @@
      * @return a Network if there is a running VPN network or null if there is no running VPN
      *         network or network is null.
      */
+    @VisibleForTesting
+    @Nullable
     public synchronized Network getNetwork() {
         final NetworkAgent agent = mNetworkAgent;
         if (null == agent) return null;
@@ -1248,8 +1250,9 @@
         mLegacyState = LegacyVpnInfo.STATE_CONNECTING;
         updateState(DetailedState.CONNECTING, "agentConnect");
 
-        NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder().build();
-        networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
+        final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder()
+                .setBypassableVpn(mConfig.allowBypass && !mLockdown)
+                .build();
 
         capsBuilder.setOwnerUid(mOwnerUID);
         capsBuilder.setAdministratorUids(new int[] {mOwnerUID});
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ac7e01e..1786a51 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -3978,6 +3978,9 @@
      * @return true if the provided key is used by the SyncManager in scheduling the sync.
      */
     private static boolean isSyncSetting(String key) {
+        if (key == null) {
+            return false;
+        }
         if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) {
             return true;
         }
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index d0e7e45..58308d8 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -250,7 +250,19 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-        ABORT_NO_ERROR,
+            NOT_HANDLED,
+            HANDLED,
+            ABORT_UNRECOGNIZED_OPCODE,
+            ABORT_NOT_IN_CORRECT_MODE,
+            ABORT_CANNOT_PROVIDE_SOURCE,
+            ABORT_INVALID_OPERAND,
+            ABORT_REFUSED,
+            ABORT_UNABLE_TO_DETERMINE,
+    })
+    public @interface HandleMessageResult {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
         ABORT_UNRECOGNIZED_OPCODE,
         ABORT_NOT_IN_CORRECT_MODE,
         ABORT_CANNOT_PROVIDE_SOURCE,
@@ -260,8 +272,11 @@
     })
     public @interface AbortReason {}
 
-    // Internal abort error code. It's the same as success.
-    static final int ABORT_NO_ERROR = -1;
+    // Indicates that a message was not handled, but could be handled by another local device.
+    // If no local devices handle the message, we send <Feature Abort>[Unrecognized Opcode].
+    static final int NOT_HANDLED = -2;
+    // Indicates that a message has been handled successfully; no feature abort needed.
+    static final int HANDLED = -1;
     // Constants related to operands of HDMI CEC commands.
     // Refer to CEC Table 29 in HDMI Spec v1.4b.
     // [Abort Reason]
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 1643ec1..ad2ef2a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -565,19 +565,24 @@
     }
 
     @ServiceThreadOnly
-    private void onReceiveCommand(HdmiCecMessage message) {
+    @VisibleForTesting
+    void onReceiveCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if ((isAcceptableAddress(message.getDestination())
-            || !mService.isAddressAllocated())
-            && mService.handleCecCommand(message)) {
+        if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) {
             return;
         }
-        // Not handled message, so we will reply it with <Feature Abort>.
-        maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+        @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message);
+        if (messageState == Constants.NOT_HANDLED) {
+            // Message was not handled
+            maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+        } else if (messageState != Constants.HANDLED) {
+            // Message handler wants to send a feature abort
+            maySendFeatureAbortCommand(message, messageState);
+        }
     }
 
     @ServiceThreadOnly
-    void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) {
+    void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) {
         assertRunOnServiceThread();
         // Swap the source and the destination.
         int src = message.getDestination();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index bdc4e66..505e743 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -248,11 +248,13 @@
      * @return true if consumed a message; otherwise, return false.
      */
     @ServiceThreadOnly
-    boolean dispatchMessage(HdmiCecMessage message) {
+    @VisibleForTesting
+    @Constants.HandleMessageResult
+    protected int dispatchMessage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int dest = message.getDestination();
         if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
-            return false;
+            return Constants.NOT_HANDLED;
         }
         // Cache incoming message if it is included in the list of cacheable opcodes.
         mCecMessageCache.cacheMessage(message);
@@ -260,10 +262,11 @@
     }
 
     @ServiceThreadOnly
-    protected final boolean onMessage(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected final int onMessage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (dispatchMessageToAction(message)) {
-            return true;
+            return Constants.HANDLED;
         }
         switch (message.getOpcode()) {
             case Constants.MESSAGE_ACTIVE_SOURCE:
@@ -357,7 +360,7 @@
             case Constants.MESSAGE_GIVE_FEATURES:
                 return handleGiveFeatures(message);
             default:
-                return false;
+                return Constants.NOT_HANDLED;
         }
     }
 
@@ -375,7 +378,8 @@
     }
 
     @ServiceThreadOnly
-    protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
+    @Constants.HandleMessageResult
+    protected int handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
         assertRunOnServiceThread();
 
         int physicalAddress = mService.getPhysicalAddress();
@@ -383,76 +387,83 @@
                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                         mAddress, physicalAddress, mDeviceType);
         mService.sendCecCommand(cecMessage, callback);
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) {
+    @Constants.HandleMessageResult
+    protected int handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) {
         assertRunOnServiceThread();
         int vendorId = mService.getVendorId();
         HdmiCecMessage cecMessage =
                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, vendorId);
         mService.sendCecCommand(cecMessage, callback);
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleGetCecVersion(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGetCecVersion(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int version = mService.getCecVersion();
         HdmiCecMessage cecMessage =
                 HdmiCecMessageBuilder.buildCecVersion(
                         message.getDestination(), message.getSource(), version);
         mService.sendCecCommand(cecMessage);
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
-    private boolean handleCecVersion() {
+    @Constants.HandleMessageResult
+    protected int handleCecVersion() {
         assertRunOnServiceThread();
 
         // Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork.
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleActiveSource(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleInactiveSource(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleInactiveSource(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRequestActiveSource(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
-        // 'return false' will cause to reply with <Feature Abort>.
-        return false;
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString());
-        // 'return false' will cause to reply with <Feature Abort>.
-        return false;
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
-    protected boolean handleGiveOsdName(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGiveOsdName(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Note that since this method is called after logical address allocation is done,
         // mDeviceInfo should not be null.
         buildAndSendSetOsdName(message.getSource());
-        return true;
+        return Constants.HANDLED;
     }
 
     protected void buildAndSendSetOsdName(int dest) {
@@ -475,18 +486,21 @@
 
     // Audio System device with no Playback device type
     // needs to refactor this function if it's also a switch
-    protected boolean handleRoutingChange(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRoutingChange(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     // Audio System device with no Playback device type
     // needs to refactor this function if it's also a switch
-    protected boolean handleRoutingInformation(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRoutingInformation(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @CallSuper
-    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportPhysicalAddress(HdmiCecMessage message) {
         // <Report Physical Address>  is also handled in HdmiCecNetwork to update the local network
         // state
 
@@ -495,7 +509,7 @@
         // Ignore if [Device Discovery Action] is going on.
         if (hasAction(DeviceDiscoveryAction.class)) {
             Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
-            return true;
+            return Constants.HANDLED;
         }
 
         HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address);
@@ -506,63 +520,77 @@
                     HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
         }
 
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleSetSystemAudioMode(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleTerminateArc(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleTerminateArc(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleInitiateArc(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleInitiateArc(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRequestArcInitiate(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleRequestArcTermination(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRequestArcTermination(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleReportArcInitiate(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleReportArcInitiate(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleReportArcTermination(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleReportArcTermination(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleReportAudioStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleGiveAudioStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleReportShortAudioDescriptor(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleReportShortAudioDescriptor(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @Constants.RcProfile
@@ -572,13 +600,14 @@
 
     protected abstract List<Integer> getDeviceFeatures();
 
-    protected boolean handleGiveFeatures(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGiveFeatures(HdmiCecMessage message) {
         if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
-            return false;
+            return Constants.ABORT_UNRECOGNIZED_OPCODE;
         }
 
         reportFeatures();
-        return true;
+        return Constants.HANDLED;
     }
 
     protected void reportFeatures() {
@@ -598,32 +627,34 @@
     }
 
     @ServiceThreadOnly
-    protected boolean handleStandby(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleStandby(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #12
         if (mService.isControlEnabled()
                 && !mService.isProhibitMode()
                 && mService.isPowerOnOrTransient()) {
             mService.standby();
-            return true;
+            return Constants.HANDLED;
         }
-        return false;
+        return Constants.ABORT_NOT_IN_CORRECT_MODE;
     }
 
     @ServiceThreadOnly
-    protected boolean handleUserControlPressed(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleUserControlPressed(HdmiCecMessage message) {
         assertRunOnServiceThread();
         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
         if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
             mService.standby();
-            return true;
+            return Constants.HANDLED;
         } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
             mService.wakeUp();
-            return true;
+            return Constants.HANDLED;
         } else if (mService.getHdmiCecVolumeControl()
                 == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand(
                 message)) {
-            return false;
+            return Constants.ABORT_REFUSED;
         }
 
         if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) {
@@ -631,7 +662,7 @@
             // keycode to Android keycode.
             // Do not <Feature Abort> as the local device should already be in the correct power
             // state.
-            return true;
+            return Constants.HANDLED;
         }
 
         final long downTime = SystemClock.uptimeMillis();
@@ -653,15 +684,15 @@
             mHandler.sendMessageDelayed(
                     Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
                     FOLLOWER_SAFETY_TIMEOUT);
-            return true;
+            return Constants.HANDLED;
         }
 
-        mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
-        return true;
+        return Constants.ABORT_INVALID_OPERAND;
     }
 
     @ServiceThreadOnly
-    protected boolean handleUserControlReleased() {
+    @Constants.HandleMessageResult
+    protected int handleUserControlReleased() {
         assertRunOnServiceThread();
         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
         mLastKeyRepeatCount = 0;
@@ -670,7 +701,7 @@
             injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
             mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     static void injectKeyEvent(long time, int action, int keycode, int repeat) {
@@ -717,38 +748,45 @@
                     || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
     }
 
-    protected boolean handleTextViewOn(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleTextViewOn(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleImageViewOn(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleImageViewOn(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleSetStreamPath(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleSetStreamPath(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGiveDevicePowerStatus(HdmiCecMessage message) {
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildReportPowerStatus(
                         mAddress, message.getSource(), mService.getPowerStatus()));
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleMenuRequest(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleMenuRequest(HdmiCecMessage message) {
         // Always report menu active to receive Remote Control.
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildReportMenuStatus(
                         mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleMenuStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleMenuStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleVendorCommand(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleVendorCommand(HdmiCecMessage message) {
         if (!mService.invokeVendorCommandListenersOnReceived(
                 mDeviceType,
                 message.getSource(),
@@ -757,57 +795,64 @@
                 false)) {
             // Vendor command listener may not have been registered yet. Respond with
             // <Feature Abort> [Refused] so that the sender can try again later.
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return Constants.ABORT_REFUSED;
         }
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleVendorCommandWithId(HdmiCecMessage message) {
         byte[] params = message.getParams();
         int vendorId = HdmiUtils.threeBytesToInt(params);
         if (vendorId == mService.getVendorId()) {
             if (!mService.invokeVendorCommandListenersOnReceived(
                     mDeviceType, message.getSource(), message.getDestination(), params, true)) {
-                mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+                return Constants.ABORT_REFUSED;
             }
         } else if (message.getDestination() != Constants.ADDR_BROADCAST
                 && message.getSource() != Constants.ADDR_UNREGISTERED) {
             Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+            return Constants.ABORT_UNRECOGNIZED_OPCODE;
         } else {
             Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     protected void sendStandby(int deviceId) {
         // Do nothing.
     }
 
-    protected boolean handleSetOsdName(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetOsdName(HdmiCecMessage message) {
         // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRecordTvScreen(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleTimerClearedStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleReportPowerStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportPowerStatus(HdmiCecMessage message) {
         // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
-        return true;
+        return Constants.HANDLED;
     }
 
-    protected boolean handleTimerStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleTimerStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
-    protected boolean handleRecordStatus(HdmiCecMessage message) {
-        return false;
+    @Constants.HandleMessageResult
+    protected int handleRecordStatus(HdmiCecMessage message) {
+        return Constants.NOT_HANDLED;
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index bf5bf8b..790c067 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -316,7 +316,8 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int logicalAddress = message.getSource();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -339,52 +340,56 @@
             mDelayedMessageBuffer.removeActiveSource();
             return super.handleActiveSource(message);
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleInitiateArc(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleInitiateArc(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // TODO(amyjojo): implement initiate arc handler
         HdmiLogger.debug(TAG + "Stub handleInitiateArc");
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportArcInitiate(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // TODO(amyjojo): implement report arc initiate handler
         HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleReportArcTermination(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportArcTermination(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // TODO(amyjojo): implement report arc terminate handler
         HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGiveAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl()
                 == HdmiControlManager.VOLUME_CONTROL_ENABLED) {
             reportAudioStatus(message.getSource());
-        } else {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return Constants.HANDLED;
         }
-        return true;
+        return Constants.ABORT_REFUSED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // If the audio system is initiating the system audio mode on and TV asks the sam status at
         // the same time, respond with true. Since we know TV supports sam in this situation.
@@ -399,52 +404,53 @@
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
                         mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn));
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRequestArcInitiate(HdmiCecMessage message) {
         assertRunOnServiceThread();
         removeAction(ArcInitiationActionFromAvr.class);
         if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+            return Constants.ABORT_UNRECOGNIZED_OPCODE;
         } else if (!isDirectConnectToTv()) {
             HdmiLogger.debug("AVR device is not directly connected with TV");
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
             addAndStartAction(new ArcInitiationActionFromAvr(this));
+            return Constants.HANDLED;
         }
-        return true;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRequestArcTermination(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRequestArcTermination(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+            return Constants.ABORT_UNRECOGNIZED_OPCODE;
         } else if (!isArcEnabled()) {
             HdmiLogger.debug("ARC is not established between TV and AVR device");
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
             removeAction(ArcTerminationActionFromAvr.class);
             addAndStartAction(new ArcTerminationActionFromAvr(this));
+            return Constants.HANDLED;
         }
-        return true;
     }
 
     @ServiceThreadOnly
-    protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
         assertRunOnServiceThread();
         HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
         if (!isSystemAudioControlFeatureEnabled()) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return true;
+            return Constants.ABORT_REFUSED;
         }
         if (!isSystemAudioActivated()) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
-            return true;
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
         }
 
         List<DeviceConfig> config = null;
@@ -468,21 +474,20 @@
         } else {
             AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
             if (deviceInfo == null) {
-                mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
-                return true;
+                return Constants.ABORT_UNABLE_TO_DETERMINE;
             }
 
             sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
         }
 
         if (sadBytes.length == 0) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+            return Constants.ABORT_INVALID_OPERAND;
         } else {
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
                             mAddress, message.getSource(), sadBytes));
+            return Constants.HANDLED;
         }
-        return true;
     }
 
     private byte[] getSupportedShortAudioDescriptors(
@@ -624,7 +629,8 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
         assertRunOnServiceThread();
         boolean systemAudioStatusOn = message.getParams().length != 0;
         // Check if the request comes from a non-TV device.
@@ -632,8 +638,7 @@
         // if non-TV device tries to turn on the feature
         if (message.getSource() != Constants.ADDR_TV) {
             if (systemAudioStatusOn) {
-                handleSystemAudioModeOnFromNonTvDevice(message);
-                return true;
+                return handleSystemAudioModeOnFromNonTvDevice(message);
             }
         } else {
             // If TV request the feature on
@@ -644,8 +649,7 @@
         // If TV or Audio System does not support the feature,
         // will send abort command.
         if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return true;
+            return Constants.ABORT_REFUSED;
         }
 
         mService.sendCecCommand(
@@ -660,7 +664,7 @@
             if (HdmiUtils.getLocalPortFromPhysicalAddress(
                     sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress())
                             != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
-                return true;
+                return Constants.HANDLED;
             }
             HdmiDeviceInfo safeDeviceInfoByPath =
                     mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
@@ -668,29 +672,31 @@
                 switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
             }
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetSystemAudioMode(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!checkSupportAndSetSystemAudioMode(
                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return Constants.ABORT_REFUSED;
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!checkSupportAndSetSystemAudioMode(
                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return Constants.ABORT_REFUSED;
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
@@ -948,13 +954,13 @@
     /**
      * Handler of System Audio Mode Request on from non TV device
      */
-    void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
         if (!isSystemAudioControlFeatureEnabled()) {
             HdmiLogger.debug(
                     "Cannot turn on" + "system audio mode "
                             + "because the System Audio Control feature is disabled.");
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return;
+            return Constants.ABORT_REFUSED;
         }
         // Wake up device
         mService.wakeUp();
@@ -967,7 +973,7 @@
             mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                     mAddress, Constants.ADDR_BROADCAST, true));
-            return;
+            return Constants.HANDLED;
         }
         // Check if TV supports System Audio Control.
         // Handle broadcasting setSystemAudioMode on or aborting message on callback.
@@ -983,6 +989,7 @@
                 }
             }
         });
+        return Constants.HANDLED;
     }
 
     void setTvSystemAudioModeSupport(boolean supported) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 2995252..10f6948f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -251,7 +251,8 @@
     }
 
     @ServiceThreadOnly
-    protected boolean handleUserControlPressed(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleUserControlPressed(HdmiCecMessage message) {
         assertRunOnServiceThread();
         wakeUpIfActiveSource();
         return super.handleUserControlPressed(message);
@@ -270,10 +271,11 @@
     }
 
     @ServiceThreadOnly
-    protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!SET_MENU_LANGUAGE) {
-            return false;
+            return Constants.ABORT_UNRECOGNIZED_OPCODE;
         }
 
         try {
@@ -283,7 +285,7 @@
                 // Do not switch language if the new language is the same as the current one.
                 // This helps avoid accidental country variant switching from en_US to en_AU
                 // due to the limitation of CEC. See the warning below.
-                return true;
+                return Constants.HANDLED;
             }
 
             // Don't use Locale.getAvailableLocales() since it returns a locale
@@ -298,36 +300,38 @@
                     // will always be mapped to en-AU among other variants like en-US, en-GB,
                     // an en-IN, which may not be the expected one.
                     LocalePicker.updateLocale(localeInfo.getLocale());
-                    return true;
+                    return Constants.HANDLED;
                 }
             }
             Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
-            return false;
+            return Constants.ABORT_INVALID_OPERAND;
         } catch (UnsupportedEncodingException e) {
             Slog.w(TAG, "Can't handle <Set Menu Language>", e);
-            return false;
+            return Constants.ABORT_INVALID_OPERAND;
         }
     }
 
     @Override
-    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetSystemAudioMode(HdmiCecMessage message) {
         // System Audio Mode only turns on/off when Audio System broadcasts on/off message.
         // For device with type 4 and 5, it can set system audio mode on/off
         // when there is another audio system device connected into the system first.
         if (message.getDestination() != Constants.ADDR_BROADCAST
                 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM
                 || mService.audioSystem() != null) {
-            return true;
+            return Constants.HANDLED;
         }
         boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
         if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
             mService.setSystemAudioActivated(setSystemAudioModeOn);
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
-    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
         // Only directly addressed System Audio Mode Status message can change internal
         // system audio mode status.
         if (message.getDestination() == mAddress
@@ -337,25 +341,27 @@
                 mService.setSystemAudioActivated(setSystemAudioModeOn);
             }
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRoutingChange(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRoutingChange(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
         handleRoutingChangeAndInformation(physicalAddress, message);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRoutingInformation(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRoutingInformation(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
         handleRoutingChangeAndInformation(physicalAddress, message);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 2ed8481..979a1d4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -203,7 +203,8 @@
     }
 
     @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int logicalAddress = message.getSource();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -215,20 +216,22 @@
         if (isRoutingControlFeatureEnabled()) {
             switchInputOnReceivingNewActivePath(physicalAddress);
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRequestActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         maySendActiveSource(message.getSource());
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSetStreamPath(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetStreamPath(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
         // If current device is the target path, set to Active Source.
@@ -242,12 +245,13 @@
             setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleSetStreamPath()");
         }
         switchInputOnReceivingNewActivePath(physicalAddress);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRoutingChange(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRoutingChange(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
         if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
@@ -256,16 +260,16 @@
             setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingChange()");
         }
         if (!isRoutingControlFeatureEnabled()) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return true;
+            return Constants.ABORT_REFUSED;
         }
         handleRoutingChangeAndInformation(physicalAddress, message);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRoutingInformation(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRoutingInformation(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
         if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
@@ -274,11 +278,10 @@
             setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingInformation()");
         }
         if (!isRoutingControlFeatureEnabled()) {
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return true;
+            return Constants.ABORT_REFUSED;
         }
         handleRoutingChangeAndInformation(physicalAddress, message);
-        return true;
+        return Constants.HANDLED;
     }
 
     // Method to switch Input with the new Active Path.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 90d6433..cd66a8f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -47,6 +47,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -210,11 +211,13 @@
 
     @Override
     @ServiceThreadOnly
-    boolean dispatchMessage(HdmiCecMessage message) {
+    @VisibleForTesting
+    @Constants.HandleMessageResult
+    protected int dispatchMessage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived()
                 && mStandbyHandler.handleCommand(message)) {
-            return true;
+            return Constants.HANDLED;
         }
         return super.onMessage(message);
     }
@@ -409,7 +412,8 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int logicalAddress = message.getSource();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -429,21 +433,22 @@
             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
             mDelayedMessageBuffer.add(message);
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleInactiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleInactiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #10
 
         // Ignore <Inactive Source> from non-active source device.
         if (getActiveSource().logicalAddress != message.getSource()) {
-            return true;
+            return Constants.HANDLED;
         }
         if (isProhibitMode()) {
-            return true;
+            return Constants.HANDLED;
         }
         int portId = getPrevPortId();
         if (portId != Constants.INVALID_PORT_ID) {
@@ -452,10 +457,10 @@
             HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
                     message.getSource());
             if (inactiveSource == null) {
-                return true;
+                return Constants.HANDLED;
             }
             if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
-                return true;
+                return Constants.HANDLED;
             }
             // TODO: Switch the TV freeze mode off
 
@@ -468,29 +473,31 @@
             setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
             mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRequestActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #19
         if (mAddress == getActiveSource().logicalAddress) {
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleGetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!broadcastMenuLanguage(mService.getLanguage())) {
             Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
@@ -506,7 +513,8 @@
     }
 
     @Override
-    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportPhysicalAddress(HdmiCecMessage message) {
         super.handleReportPhysicalAddress(message);
         int path = HdmiUtils.twoBytesToInt(message.getParams());
         int address = message.getSource();
@@ -516,19 +524,21 @@
             handleNewDeviceAtTheTailOfActivePath(path);
         }
         startNewDeviceAction(ActiveSource.of(address, path), type);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
-    protected boolean handleTimerStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleTimerStatus(HdmiCecMessage message) {
         // Do nothing.
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
-    protected boolean handleRecordStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRecordStatus(HdmiCecMessage message) {
         // Do nothing.
-        return true;
+        return Constants.HANDLED;
     }
 
     void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
@@ -590,7 +600,8 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleRoutingChange(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRoutingChange(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #21
         byte[] params = message.getParams();
@@ -601,27 +612,29 @@
             int newPath = HdmiUtils.twoBytesToInt(params, 2);
             addAndStartAction(new RoutingControlAction(this, newPath, true, null));
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleReportAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (mService.getHdmiCecVolumeControl()
                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
-            return false;
+            return Constants.ABORT_REFUSED;
         }
 
         boolean mute = HdmiUtils.isAudioStatusMute(message);
         int volume = HdmiUtils.getAudioStatusVolume(message);
         setAudioStatus(mute, volume);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleTextViewOn(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleTextViewOn(HdmiCecMessage message) {
         assertRunOnServiceThread();
 
         // Note that <Text View On> (and <Image View On>) command won't be handled here in
@@ -634,12 +647,13 @@
         if (mService.isPowerStandbyOrTransient() && getAutoWakeup()) {
             mService.wakeUp();
         }
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleImageViewOn(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleImageViewOn(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Currently, it's the same as <Text View On>.
         return handleTextViewOn(message);
@@ -977,7 +991,8 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleInitiateArc(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleInitiateArc(HdmiCecMessage message) {
         assertRunOnServiceThread();
 
         if (!canStartArcUpdateAction(message.getSource(), true)) {
@@ -985,13 +1000,12 @@
             if (avrDeviceInfo == null) {
                 // AVR may not have been discovered yet. Delay the message processing.
                 mDelayedMessageBuffer.add(message);
-                return true;
+                return Constants.HANDLED;
             }
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
             }
-            return true;
+            return Constants.ABORT_REFUSED;
         }
 
         // In case where <Initiate Arc> is started by <Request ARC Initiation>
@@ -1000,7 +1014,7 @@
         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                 message.getSource(), true);
         addAndStartAction(action);
-        return true;
+        return Constants.HANDLED;
     }
 
     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
@@ -1022,11 +1036,12 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleTerminateArc(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleTerminateArc(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (mService .isPowerStandbyOrTransient()) {
             setArcStatus(false);
-            return true;
+            return Constants.HANDLED;
         }
         // Do not check ARC configuration since the AVR might have been already removed.
         // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
@@ -1035,12 +1050,13 @@
         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                 message.getSource(), false);
         addAndStartAction(action);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSetSystemAudioMode(HdmiCecMessage message) {
         assertRunOnServiceThread();
         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
         if (!isMessageForSystemAudio(message)) {
@@ -1049,30 +1065,29 @@
                 mDelayedMessageBuffer.add(message);
             } else {
                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
-                mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+                return Constants.ABORT_REFUSED;
             }
-            return true;
         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
                     + "because the System Audio Control feature is disabled: %s", message);
-            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
-            return true;
+            return Constants.ABORT_REFUSED;
         }
         removeAction(SystemAudioAutoInitiationAction.class);
         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
                 message.getSource(), systemAudioStatus, null);
         addAndStartAction(action);
-        return true;
+        return Constants.HANDLED;
     }
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (!isMessageForSystemAudio(message)) {
             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
             // Ignore this message.
-            return true;
+            return Constants.HANDLED;
         }
         boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled();
         boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
@@ -1089,13 +1104,14 @@
             setSystemAudioMode(tvSystemAudioMode);
         }
 
-        return true;
+        return Constants.HANDLED;
     }
 
     // Seq #53
     @Override
     @ServiceThreadOnly
-    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleRecordTvScreen(HdmiCecMessage message) {
         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
         if (!actions.isEmpty()) {
             // Assumes only one OneTouchRecordAction.
@@ -1107,25 +1123,21 @@
             }
             // 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 Constants.ABORT_CANNOT_PROVIDE_SOURCE;
         }
 
         int recorderAddress = message.getSource();
         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
-        int reason = startOneTouchRecord(recorderAddress, recordSource);
-        if (reason != Constants.ABORT_NO_ERROR) {
-            mService.maySendFeatureAbortCommand(message, reason);
-        }
-        return true;
+        return startOneTouchRecord(recorderAddress, recordSource);
     }
 
     @Override
-    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleTimerClearedStatus(HdmiCecMessage message) {
         byte[] params = message.getParams();
         int timerClearedStatusData = params[0] & 0xFF;
         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
-        return true;
+        return Constants.HANDLED;
     }
 
     void announceOneTouchRecordResult(int recorderAddress, int result) {
@@ -1337,6 +1349,7 @@
 
     // Seq #54 and #55
     @ServiceThreadOnly
+    @Constants.HandleMessageResult
     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
         assertRunOnServiceThread();
         if (!mService.isControlEnabled()) {
@@ -1362,7 +1375,7 @@
         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
                 + Arrays.toString(recordSource));
-        return Constants.ABORT_NO_ERROR;
+        return Constants.HANDLED;
     }
 
     @ServiceThreadOnly
@@ -1494,9 +1507,10 @@
     }
 
     @Override
-    protected boolean handleMenuStatus(HdmiCecMessage message) {
+    @Constants.HandleMessageResult
+    protected int handleMenuStatus(HdmiCecMessage message) {
         // Do nothing and just return true not to prevent from responding <Feature Abort>.
-        return true;
+        return Constants.HANDLED;
     }
 
     @Constants.RcProfile
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 03a8338..031c057 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -372,8 +372,7 @@
 
     private HdmiCecMessageValidator mMessageValidator;
 
-    private final HdmiCecPowerStatusController mPowerStatusController =
-            new HdmiCecPowerStatusController(this);
+    private HdmiCecPowerStatusController mPowerStatusController;
 
     @ServiceThreadOnly
     private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -427,7 +426,7 @@
     // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing.
     private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter();
 
-    private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer(this);
+    private CecMessageBuffer mCecMessageBuffer;
 
     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
 
@@ -493,6 +492,9 @@
             mIoLooper = mIoThread.getLooper();
         }
 
+        if (mPowerStatusController == null) {
+            mPowerStatusController = new HdmiCecPowerStatusController(this);
+        }
         mPowerStatusController.setPowerStatus(getInitialPowerStatus());
         mProhibitMode = false;
         mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
@@ -501,6 +503,9 @@
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
 
+        if (mCecMessageBuffer == null) {
+            mCecMessageBuffer = new CecMessageBuffer(this);
+        }
         if (mCecController == null) {
             mCecController = HdmiCecController.create(this, getAtomWriter());
         }
@@ -948,11 +953,10 @@
 
     /**
      * Returns {@link Looper} for IO operation.
-     *
-     * <p>Declared as package-private.
      */
     @Nullable
-    Looper getIoLooper() {
+    @VisibleForTesting
+    protected Looper getIoLooper() {
         return mIoLooper;
     }
 
@@ -974,10 +978,9 @@
     /**
      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
      * for tasks that are running on main service thread.
-     *
-     * <p>Declared as package-private.
      */
-    Looper getServiceLooper() {
+    @VisibleForTesting
+    protected Looper getServiceLooper() {
         return mHandler.getLooper();
     }
 
@@ -1015,8 +1018,9 @@
     /**
      * Returns version of CEC.
      */
+    @VisibleForTesting
     @HdmiControlManager.HdmiCecVersion
-    int getCecVersion() {
+    protected int getCecVersion() {
         return mCecVersion;
     }
 
@@ -1087,23 +1091,30 @@
     }
 
     @ServiceThreadOnly
-    boolean handleCecCommand(HdmiCecMessage message) {
+    @VisibleForTesting
+    @Constants.HandleMessageResult
+    protected int handleCecCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int errorCode = mMessageValidator.isValid(message);
         if (errorCode != HdmiCecMessageValidator.OK) {
             // We'll not response on the messages with the invalid source or destination
             // or with parameter length shorter than specified in the standard.
             if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
-                maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+                return Constants.ABORT_INVALID_OPERAND;
             }
-            return true;
+            return Constants.HANDLED;
         }
         getHdmiCecNetwork().handleCecMessage(message);
-        if (dispatchMessageToLocalDevice(message)) {
-            return true;
+
+        @Constants.HandleMessageResult int handleMessageResult =
+                dispatchMessageToLocalDevice(message);
+        if (handleMessageResult == Constants.NOT_HANDLED
+                && !mAddressAllocated
+                && mCecMessageBuffer.bufferMessage(message)) {
+            return Constants.HANDLED;
         }
 
-        return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
+        return handleMessageResult;
     }
 
     void enableAudioReturnChannel(int portId, boolean enabled) {
@@ -1111,19 +1122,25 @@
     }
 
     @ServiceThreadOnly
-    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
+    @VisibleForTesting
+    @Constants.HandleMessageResult
+    protected int dispatchMessageToLocalDevice(HdmiCecMessage message) {
         assertRunOnServiceThread();
         for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
-            if (device.dispatchMessage(message)
+            @Constants.HandleMessageResult int messageResult = device.dispatchMessage(message);
+            if (messageResult != Constants.NOT_HANDLED
                     && message.getDestination() != Constants.ADDR_BROADCAST) {
-                return true;
+                return messageResult;
             }
         }
 
-        if (message.getDestination() != Constants.ADDR_BROADCAST) {
+        // We should never respond <Feature Abort> to a broadcast message
+        if (message.getDestination() == Constants.ADDR_BROADCAST) {
+            return Constants.HANDLED;
+        } else {
             HdmiLogger.warning("Unhandled cec command:" + message);
+            return Constants.NOT_HANDLED;
         }
-        return false;
     }
 
     /**
@@ -2970,7 +2987,7 @@
     }
 
     @VisibleForTesting
-    boolean isStandbyMessageReceived() {
+    protected boolean isStandbyMessageReceived() {
         return mStandbyMessageReceived;
     }
 
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 c44089b..f173fc7 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -691,6 +691,7 @@
             sendLocationSettingUpdate();
             sendWifiSettingUpdate(true /* forceUpdate */);
             sendAirplaneModeSettingUpdate();
+            sendMicrophoneDisableSettingUpdateForCurrentUser();
 
             mTransactionManager.onHubReset();
             queryNanoAppsInternal(contextHubId);
@@ -1123,6 +1124,7 @@
      */
     public void onUserChanged() {
         Log.d(TAG, "User changed to id: " + getCurrentUserId());
+        sendLocationSettingUpdate();
         sendMicrophoneDisableSettingUpdateForCurrentUser();
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 6e99cba..76ecc1a 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -15,14 +15,17 @@
  */
 
 package com.android.server.locksettings;
+
 import static android.os.UserHandle.USER_SYSTEM;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -35,6 +38,8 @@
 import com.android.internal.widget.RebootEscrowListener;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -65,6 +70,22 @@
     public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count";
 
     static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp";
+    static final String REBOOT_ESCROW_KEY_PROVIDER = "reboot_escrow_key_provider";
+
+    /**
+     * The verified boot 2.0 vbmeta digest of the current slot, the property value is always
+     * available after boot.
+     */
+    static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest";
+    /**
+     * The system prop contains vbmeta digest of the inactive slot. The build property is set after
+     * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value
+     * is available for vbmeta digest verification after the device reboots.
+     */
+    static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest";
+    static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest";
+    static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST =
+            "reboot_escrow_key_other_vbmeta_digest";
 
     /**
      * Number of boots until we consider the escrow data to be stale for the purposes of metrics.
@@ -86,6 +107,31 @@
     private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
     private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
 
+    @IntDef(prefix = {"ERROR_"}, value = {
+            ERROR_NONE,
+            ERROR_UNKNOWN,
+            ERROR_NO_PROVIDER,
+            ERROR_LOAD_ESCROW_KEY,
+            ERROR_RETRY_COUNT_EXHAUSTED,
+            ERROR_UNLOCK_ALL_USERS,
+            ERROR_PROVIDER_MISMATCH,
+            ERROR_KEYSTORE_FAILURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RebootEscrowErrorCode {
+    }
+
+    static final int ERROR_NONE = 0;
+    static final int ERROR_UNKNOWN = 1;
+    static final int ERROR_NO_PROVIDER = 2;
+    static final int ERROR_LOAD_ESCROW_KEY = 3;
+    static final int ERROR_RETRY_COUNT_EXHAUSTED = 4;
+    static final int ERROR_UNLOCK_ALL_USERS = 5;
+    static final int ERROR_PROVIDER_MISMATCH = 6;
+    static final int ERROR_KEYSTORE_FAILURE = 7;
+
+    private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
+
     /**
      * Logs events for later debugging in bugreports.
      */
@@ -199,6 +245,10 @@
                     0);
         }
 
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
         public int getLoadEscrowDataRetryLimit() {
             return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
                     "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
@@ -221,6 +271,11 @@
         public RebootEscrowEventLog getEventLog() {
             return new RebootEscrowEventLog();
         }
+
+        public String getVbmetaDigest(boolean other) {
+            return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME)
+                    : SystemProperties.get(VBMETA_DIGEST_PROP_NAME);
+        }
     }
 
     RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
@@ -261,6 +316,7 @@
         if (rebootEscrowUsers.isEmpty()) {
             Slog.i(TAG, "No reboot escrow data found for users,"
                     + " skipping loading escrow data");
+            clearMetricsStorage();
             return;
         }
 
@@ -284,6 +340,7 @@
         }
 
         Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+        mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
         onGetRebootEscrowKeyFailed(users, attemptNumber);
     }
 
@@ -307,6 +364,17 @@
         }
 
         if (escrowKey == null) {
+            if (mLoadEscrowDataErrorCode == ERROR_NONE) {
+                // Specifically check if the RoR provider has changed after reboot.
+                int providerType = mInjector.serverBasedResumeOnReboot()
+                        ? RebootEscrowProviderInterface.TYPE_SERVER_BASED
+                        : RebootEscrowProviderInterface.TYPE_HAL;
+                if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) {
+                    mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH;
+                } else {
+                    mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY;
+                }
+            }
             onGetRebootEscrowKeyFailed(users, attemptNumber + 1);
             return;
         }
@@ -321,9 +389,49 @@
         // Clear the old key in keystore. A new key will be generated by new RoR requests.
         mKeyStoreManager.clearKeyStoreEncryptionKey();
 
+        if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) {
+            mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS;
+        }
         onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1);
     }
 
+    private void clearMetricsStorage() {
+        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+        mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
+        mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM);
+        mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM);
+        mStorage.removeKey(REBOOT_ESCROW_KEY_PROVIDER, USER_SYSTEM);
+    }
+
+    private int getVbmetaDigestStatusOnRestoreComplete() {
+        String currentVbmetaDigest = mInjector.getVbmetaDigest(false);
+        String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+                "", USER_SYSTEM);
+        String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
+                "", USER_SYSTEM);
+
+        // The other vbmeta digest is never set, assume no slot switch is attempted.
+        if (vbmetaDigestOtherStored.isEmpty()) {
+            if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
+                return FrameworkStatsLog
+                        .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+            }
+            return FrameworkStatsLog
+                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
+        }
+
+        // The other vbmeta digest is set, we expect to boot into the new slot.
+        if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) {
+            return FrameworkStatsLog
+                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+        } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
+            return FrameworkStatsLog
+                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT;
+        }
+        return FrameworkStatsLog
+                .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
+    }
+
     private void reportMetricOnRestoreComplete(boolean success, int attemptCount) {
         int serviceType = mInjector.serverBasedResumeOnReboot()
                 ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED
@@ -331,26 +439,32 @@
 
         long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1,
                 USER_SYSTEM);
-        mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
-        int escrowDurationInSeconds = armedTimestamp != -1
-                ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1;
+        int escrowDurationInSeconds = -1;
+        long currentTimeStamp = mInjector.getCurrentTimeMillis();
+        if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) {
+            escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000;
+        }
 
-        // TODO(b/179105110) design error code; and report the true value for other fields.
-        int vbmetaDigestStatus = FrameworkStatsLog
-                .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+        int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete();
+        if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) {
+            mLoadEscrowDataErrorCode = ERROR_UNKNOWN;
+        }
 
-        mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount,
+        // TODO(179105110) report the duration since boot complete.
+        mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount,
                 escrowDurationInSeconds, vbmetaDigestStatus, -1);
+
+        mLoadEscrowDataErrorCode = ERROR_NONE;
     }
 
     private void onEscrowRestoreComplete(boolean success, int attemptCount) {
         int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM);
-        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
 
         int bootCountDelta = mInjector.getBootCount() - previousBootCount;
         if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) {
             reportMetricOnRestoreComplete(success, attemptCount);
         }
+        clearMetricsStorage();
     }
 
     private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
@@ -358,6 +472,14 @@
         if (rebootEscrowProvider == null) {
             Slog.w(TAG,
                     "Had reboot escrow data for users, but RebootEscrowProvider is unavailable");
+            mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER;
+            return null;
+        }
+
+        // Server based RoR always need the decryption key from keystore.
+        if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED
+                && kk == null) {
+            mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE;
             return null;
         }
 
@@ -463,7 +585,7 @@
             return;
         }
 
-        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+        clearMetricsStorage();
         rebootEscrowProvider.clearRebootEscrowKey();
 
         List<UserInfo> users = mUserManager.getUsers();
@@ -486,6 +608,9 @@
             return false;
         }
 
+        int actualProviderType = rebootEscrowProvider.getType();
+        // TODO(b/183140900) Fail the reboot if provider type mismatches.
+
         RebootEscrowKey escrowKey;
         synchronized (mKeyGenerationLock) {
             escrowKey = mPendingRebootEscrowKey;
@@ -505,8 +630,14 @@
         boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk);
         if (armedRebootEscrow) {
             mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
-            mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(),
+            mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(),
                     USER_SYSTEM);
+            // Store the vbmeta digest of both slots.
+            mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false),
+                    USER_SYSTEM);
+            mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
+                    mInjector.getVbmetaDigest(true), USER_SYSTEM);
+            mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM);
             mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
         }
 
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 4b00772..e8f6f4a 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -60,6 +60,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_HAL;
+    }
+
+    @Override
     public boolean hasRebootEscrowSupport() {
         return mInjector.getRebootEscrow() != null;
     }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index af6faad..e106d81 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,7 +16,11 @@
 
 package com.android.server.locksettings;
 
+import android.annotation.IntDef;
+
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 import javax.crypto.SecretKey;
 
@@ -28,6 +32,21 @@
  * @hide
  */
 public interface RebootEscrowProviderInterface {
+    @IntDef(prefix = {"TYPE_"}, value = {
+            TYPE_HAL,
+            TYPE_SERVER_BASED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RebootEscrowProviderType {
+    }
+    int TYPE_HAL = 0;
+    int TYPE_SERVER_BASED = 1;
+
+    /**
+     * Returns the reboot escrow provider type.
+     */
+    @RebootEscrowProviderType int getType();
+
     /**
      * Returns true if the secure store/discard of reboot escrow key is supported.
      */
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index 697bf08..2866987 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -95,6 +95,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_SERVER_BASED;
+    }
+
+    @Override
     public boolean hasRebootEscrowSupport() {
         return mInjector.getServiceConnection() != null;
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index db2e908..a30d993 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1225,6 +1225,70 @@
         }
 
         @Override
+        public MediaSession.Token getMediaKeyEventSession() {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + " get the media key event session");
+                }
+                MediaSessionRecordImpl record;
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null) {
+                        Log.w(TAG, "No matching user record to get the media key event session"
+                                + ", userId=" + userId);
+                        return null;
+                    }
+                    record = user.getMediaButtonSessionLocked();
+                }
+                if (record instanceof MediaSessionRecord) {
+                    return ((MediaSessionRecord) record).getSessionToken();
+                }
+                //TODO: Handle media session 2 case
+                return null;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public String getMediaKeyEventSessionPackageName() {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + " get the media key event session package");
+                }
+                MediaSessionRecordImpl record;
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null) {
+                        Log.w(TAG, "No matching user record to get the media key event session"
+                                + " package , userId=" + userId);
+                        return "";
+                    }
+                    record = user.getMediaButtonSessionLocked();
+                    if (record instanceof MediaSessionRecord) {
+                        return record.getPackageName();
+                    //TODO: Handle media session 2 case
+                    } else if (user.mLastMediaButtonReceiverHolder != null) {
+                        return user.mLastMediaButtonReceiverHolder.getPackageName();
+                    }
+                }
+                return "";
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void addSessionsListener(IActiveSessionsListener listener,
                 ComponentName componentName, int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 39ed7e8..2e4d41c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -96,9 +96,10 @@
 
     /**
      *  Notifies that the specified {@link NetworkStatsProvider} has reached its quota
-     *  which was set through {@link NetworkStatsProvider#onSetLimit(String, long)}.
+     *  which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+     *  {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
      *
      * @param tag the human readable identifier of the custom network stats provider.
      */
-    public abstract void onStatsProviderLimitReached(@NonNull String tag);
+    public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 16eac91..3193e63 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -430,7 +430,7 @@
     private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
     private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
     private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
-    private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
+    private static final int MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED = 20;
     // TODO: Add similar docs for other messages.
     /**
      * Message to indicate that reasons for why an uid is blocked changed.
@@ -4970,7 +4970,7 @@
                     mListeners.finishBroadcast();
                     return true;
                 }
-                case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+                case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: {
                     mNetworkStats.forceUpdate();
 
                     synchronized (mNetworkPoliciesSecondLock) {
@@ -5726,9 +5726,9 @@
         }
 
         @Override
-        public void onStatsProviderLimitReached(@NonNull String tag) {
-            Log.v(TAG, "onStatsProviderLimitReached: " + tag);
-            mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+        public void onStatsProviderWarningOrLimitReached(@NonNull String tag) {
+            Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag);
+            mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index fe43c31..785e487 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1676,7 +1676,9 @@
         @Override
         public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
             if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
-            invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetLimit(iface, quota));
+            // TODO: Set warning accordingly.
+            invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
+                    NetworkStatsProvider.QUOTA_UNLIMITED, quota));
         }
     }
 
@@ -2071,10 +2073,10 @@
         }
 
         @Override
-        public void notifyLimitReached() {
-            Log.d(TAG, mTag + ": onLimitReached");
+        public void notifyWarningOrLimitReached() {
+            Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
             LocalServices.getService(NetworkPolicyManagerInternal.class)
-                    .onStatsProviderLimitReached(mTag);
+                    .onStatsProviderWarningOrLimitReached(mTag);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 619fc4e..dc3b78a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -33,6 +33,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -73,7 +74,6 @@
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -184,6 +184,8 @@
 
     private Map<String, List<String>> mOemLockedApps = new HashMap();
 
+    private int mCurrentUserId = UserHandle.USER_NULL;
+
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
             ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger,
             AppOpsManager appOpsManager,
@@ -199,7 +201,8 @@
         updateBadgingEnabled();
         updateBubblesEnabled();
         updateMediaNotificationFilteringEnabled();
-        syncChannelsBypassingDnd(mContext.getUserId());
+        mCurrentUserId = ActivityManager.getCurrentUser();
+        syncChannelsBypassingDnd();
     }
 
     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -806,7 +809,7 @@
                     // but the system can
                     if (group.isBlocked() != oldGroup.isBlocked()) {
                         group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
-                        updateChannelsBypassingDnd(mContext.getUserId());
+                        updateChannelsBypassingDnd();
                     }
                 }
             }
@@ -888,13 +891,13 @@
                 // fields on the channel yet
                 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
                     boolean bypassDnd = channel.canBypassDnd();
-                    if (bypassDnd != existing.canBypassDnd()) {
+                    if (bypassDnd != existing.canBypassDnd() || wasUndeleted) {
                         existing.setBypassDnd(bypassDnd);
                         needsPolicyFileChange = true;
 
                         if (bypassDnd != mAreChannelsBypassingDnd
                                 || previousExistingImportance != existing.getImportance()) {
-                            updateChannelsBypassingDnd(mContext.getUserId());
+                            updateChannelsBypassingDnd();
                         }
                     }
                 }
@@ -958,7 +961,7 @@
 
             r.channels.put(channel.getId(), channel);
             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
-                updateChannelsBypassingDnd(mContext.getUserId());
+                updateChannelsBypassingDnd();
             }
             MetricsLogger.action(getChannelLog(channel, pkg).setType(
                     com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -1047,7 +1050,7 @@
 
             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
                     || channel.getImportance() != updatedChannel.getImportance()) {
-                updateChannelsBypassingDnd(mContext.getUserId());
+                updateChannelsBypassingDnd();
             }
         }
         updateConfig();
@@ -1145,7 +1148,7 @@
             mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
 
             if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
-                updateChannelsBypassingDnd(mContext.getUserId());
+                updateChannelsBypassingDnd();
             }
         }
     }
@@ -1512,7 +1515,7 @@
                 }
             }
             if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
-                updateChannelsBypassingDnd(mContext.getUserId());
+                updateChannelsBypassingDnd();
             }
             return deletedChannelIds;
         }
@@ -1658,29 +1661,29 @@
     }
 
     /**
-     * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
+     * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
      * updating
-     * @param userId
      */
-    private void syncChannelsBypassingDnd(int userId) {
+    private void syncChannelsBypassingDnd() {
         mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
                 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
-        updateChannelsBypassingDnd(userId);
+        updateChannelsBypassingDnd();
     }
 
     /**
-     * Updates the user's NotificationPolicy based on whether the given userId
+     * Updates the user's NotificationPolicy based on whether the current userId
      * has channels bypassing DND
      * @param userId
      */
-    private void updateChannelsBypassingDnd(int userId) {
+    private void updateChannelsBypassingDnd() {
         synchronized (mPackagePreferences) {
             final int numPackagePreferences = mPackagePreferences.size();
             for (int i = 0; i < numPackagePreferences; i++) {
                 final PackagePreferences r = mPackagePreferences.valueAt(i);
-                // Package isn't associated with this userId or notifications from this package are
-                // blocked
-                if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+                // Package isn't associated with the current userId or notifications from this
+                // package are blocked
+                if (mCurrentUserId != UserHandle.getUserId(r.uid)
+                        || r.importance == IMPORTANCE_NONE) {
                     continue;
                 }
 
@@ -2226,14 +2229,16 @@
      * Called when user switches
      */
     public void onUserSwitched(int userId) {
-        syncChannelsBypassingDnd(userId);
+        mCurrentUserId = userId;
+        syncChannelsBypassingDnd();
     }
 
     /**
      * Called when user is unlocked
      */
     public void onUserUnlocked(int userId) {
-        syncChannelsBypassingDnd(userId);
+        mCurrentUserId = userId;
+        syncChannelsBypassingDnd();
     }
 
     public void onUserRemoved(int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 7aaab0c..e0a39f3 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -260,7 +260,8 @@
 
                 // Only report metrics for base apk for now.
                 // TODO: add ISA and APK type to metrics.
-                if (pkg.getBaseApkPath().equals(path)) {
+                // OTAPreopt doesn't have stats so don't report in that case.
+                if (pkg.getBaseApkPath().equals(path) && packageStats != null) {
                     Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
                     try {
                         long sessionId = Math.randomLongInternal();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index bafe987..67638bc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1212,12 +1212,18 @@
             mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
                     + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
         } else {
-            // For incremental installs, continue publishing the install progress during committing.
-            mProgress = mIncrementalProgress;
+            // For incremental install, continue to publish incremental progress during committing.
+            if (isIncrementalInstallation() && (mIncrementalProgress - mProgress) >= 0.01) {
+                // It takes some time for data loader to write to incremental file system, so at the
+                // beginning of the commit, the incremental progress might be very small.
+                // Wait till the incremental progress is larger than what's already displayed.
+                // This way we don't see the progress ring going backwards.
+                mProgress = mIncrementalProgress;
+            }
         }
 
         // Only publish when meaningful change
-        if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
+        if (forcePublish || (mProgress - mReportedProgress) >= 0.01) {
             mReportedProgress = mProgress;
             mCallback.onSessionProgressChanged(this, mProgress);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b27c0bd..764fa02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -252,7 +252,6 @@
 import android.content.pm.parsing.component.ParsedProcess;
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
-import android.content.pm.parsing.component.ParsedUsesPermission;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Resources;
@@ -371,6 +370,7 @@
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.Watchdog;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
 import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -778,16 +778,6 @@
 
     private static final String COMPANION_PACKAGE_NAME = "com.android.companiondevicemanager";
 
-    /** Canonical intent used to identify what counts as a "web browser" app */
-    private static final Intent sBrowserIntent;
-    static {
-        sBrowserIntent = new Intent();
-        sBrowserIntent.setAction(Intent.ACTION_VIEW);
-        sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
-        sBrowserIntent.setData(Uri.parse("http:"));
-        sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL);
-    }
-
     // Compilation reasons.
     public static final int REASON_UNKNOWN = -1;
     public static final int REASON_FIRST_BOOT = 0;
@@ -5636,7 +5626,7 @@
             // Work that needs to happen on first install within each user
             if (firstUserIds != null && firstUserIds.length > 0) {
                 for (int userId : firstUserIds) {
-                    clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+                    restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
                             pkgSetting.getInstallReason(userId), userId);
                 }
             }
@@ -7752,19 +7742,6 @@
         return matches.get(0).getComponentInfo().getComponentName();
     }
 
-    private boolean packageIsBrowser(String packageName, int userId) {
-        List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
-                PackageManager.MATCH_ALL, userId);
-        final int N = list.size();
-        for (int i = 0; i < N; i++) {
-            ResolveInfo info = list.get(i);
-            if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -12304,9 +12281,15 @@
 
     public ArraySet<String> getOptimizablePackages() {
         ArraySet<String> pkgs = new ArraySet<>();
+        final boolean hibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+        AppHibernationManagerInternal appHibernationManager =
+                mInjector.getLocalService(AppHibernationManagerInternal.class);
         synchronized (mLock) {
             for (AndroidPackage p : mPackages.values()) {
-                if (PackageDexOptimizer.canOptimizePackage(p)) {
+                // Checking hibernation state is an inexpensive call.
+                boolean isHibernating = hibernationEnabled
+                        && appHibernationManager.isHibernatingGlobally(p.getPackageName());
+                if (PackageDexOptimizer.canOptimizePackage(p) && !isHibernating) {
                     pkgs.add(p.getPackageName());
                 }
             }
@@ -15459,16 +15442,55 @@
                 null);
     }
 
-    private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
             boolean suspended) {
-        final Bundle extras = new Bundle(3);
-        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
-        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
-        sendPackageBroadcast(
-                suspended ? Intent.ACTION_PACKAGES_SUSPENDED
-                        : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
-                new int[] {userId}, null, null, null);
+        final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
+        final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
+        final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
+        final int[] userIds = new int[] {userId};
+        // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
+        // allow lists are the same.
+        synchronized (mLock) {
+            for (int i = 0; i < pkgList.length; i++) {
+                final String pkgName = pkgList[i];
+                final int uid = uidList[i];
+                SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
+                        getPackageSettingInternal(pkgName, Process.SYSTEM_UID),
+                        userIds, mSettings.getPackagesLocked());
+                if (allowList == null) {
+                    allowList = new SparseArray<>(0);
+                }
+                boolean merged = false;
+                for (int j = 0; j < allowListsToSend.size(); j++) {
+                    if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
+                        pkgsToSend.get(j).add(pkgName);
+                        uidsToSend.get(j).add(uid);
+                        merged = true;
+                        break;
+                    }
+                }
+                if (!merged) {
+                    pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
+                    uidsToSend.add(IntArray.wrap(new int[] {uid}));
+                    allowListsToSend.add(allowList);
+                }
+            }
+        }
+
+        for (int i = 0; i < pkgsToSend.size(); i++) {
+            final Bundle extras = new Bundle(3);
+            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                    pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
+            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
+            final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
+                    ? null : allowListsToSend.get(i);
+            sendPackageBroadcast(
+                    suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+                            : Intent.ACTION_PACKAGES_UNSUSPENDED,
+                    null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+                    userIds, null, allowList, null);
+        }
     }
 
     /**
@@ -15612,7 +15634,7 @@
 
                 PostInstallData postInstallData =
                         new PostInstallData(null, res, () -> {
-                            clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+                            restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
                                     pkgSetting.getInstallReason(userId), userId);
                             if (intentSender != null) {
                                 onRestoreComplete(res.returnCode, mContext, intentSender);
@@ -21182,7 +21204,6 @@
                 final SparseBooleanArray changedUsers = new SparseBooleanArray();
                 synchronized (mLock) {
                     mDomainVerificationManager.clearPackage(deletedPs.name);
-                    clearDefaultBrowserIfNeeded(packageName);
                     mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
                     mAppsFilter.removePackage(getPackageSetting(packageName));
                     removedAppId = mSettings.removePackageLPw(packageName);
@@ -21740,7 +21761,6 @@
                 destroyAppDataLIF(pkg, nextUserId,
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
             }
-            clearDefaultBrowserIfNeededForUser(ps.name, nextUserId);
             removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), nextUserId, ps.appId);
             clearPackagePreferredActivities(ps.name, nextUserId);
             mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
@@ -22257,36 +22277,8 @@
         mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
     }
 
-    /** Clears state for all users, and touches intent filter verification policy */
-    void clearDefaultBrowserIfNeeded(String packageName) {
-        for (int oneUserId : mUserManager.getUserIds()) {
-            clearDefaultBrowserIfNeededForUser(packageName, oneUserId);
-        }
-    }
-
-    private void clearDefaultBrowserIfNeededForUser(String packageName, int userId) {
-        final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(userId);
-        if (!TextUtils.isEmpty(defaultBrowserPackageName)) {
-            if (packageName.equals(defaultBrowserPackageName)) {
-                mDefaultAppProvider.setDefaultBrowser(null, true, userId);
-            }
-        }
-    }
-
-    private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName,
+    private void restorePermissionsAndUpdateRolesForNewUserInstall(String packageName,
             int installReason, @UserIdInt int userId) {
-        // If this app is a browser and it's newly-installed for some
-        // users, clear any default-browser state in those users. The
-        // app's nature doesn't depend on the user, so we can just check
-        // its browser nature in any user and generalize.
-        if (packageIsBrowser(packageName, userId)) {
-            // If this browser is restored from user's backup, do not clear
-            // default-browser state for this user
-            if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
-                mDefaultAppProvider.setDefaultBrowser(null, true, userId);
-            }
-        }
-
         // We may also need to apply pending (restored) runtime permission grants
         // within these users.
         mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
@@ -22320,11 +22312,6 @@
                 }
             }
             updateDefaultHomeNotLocked(userId);
-            // TODO: We have to reset the default SMS and Phone. This requires
-            // significant refactoring to keep all default apps in the package
-            // manager (cleaner but more work) or have the services provide
-            // callbacks to the package manager to request a default app reset.
-            mDefaultAppProvider.setDefaultBrowser(null, true, userId);
             resetNetworkPolicies(userId);
             synchronized (mLock) {
                 scheduleWritePackageRestrictionsLocked(userId);
@@ -27329,6 +27316,11 @@
             return PackageManagerService.this.getPackageStartability(
                     packageName, callingUid, userId) == PACKAGE_STARTABILITY_FROZEN;
         }
+
+        @Override
+        public void deleteOatArtifactsOfPackage(String packageName) {
+            PackageManagerService.this.deleteOatArtifactsOfPackage(packageName);
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b5765b5..0ddb6cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -104,8 +104,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
-import com.android.server.pm.verify.domain.DomainVerificationShell;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
 
 import dalvik.system.DexFile;
 
@@ -2251,8 +2251,8 @@
             }
         }
 
-        final String packageName = getNextArg();
-        if (packageName == null) {
+        final List<String> packageNames = getRemainingArgs();
+        if (packageNames.isEmpty()) {
             pw.println("Error: package name not specified");
             return 1;
         }
@@ -2270,12 +2270,15 @@
         try {
             final int translatedUserId =
                     translateUserId(userId, UserHandle.USER_NULL, "runSuspend");
-            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
-                    ((appExtras.size() > 0) ? appExtras : null),
+            mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
+                    suspendedState, ((appExtras.size() > 0) ? appExtras : null),
                     ((launcherExtras.size() > 0) ? launcherExtras : null),
                     info, callingPackage, translatedUserId);
-            pw.println("Package " + packageName + " new suspended state: "
-                    + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+            for (int i = 0; i < packageNames.size(); i++) {
+                final String packageName = packageNames.get(i);
+                pw.println("Package " + packageName + " new suspended state: "
+                        + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+            }
             return 0;
         } catch (RemoteException | IllegalArgumentException e) {
             pw.println(e.toString());
@@ -3643,11 +3646,11 @@
         pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
         pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
         pw.println("");
-        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Suspends the specified package (as user).");
+        pw.println("  suspend [--user USER_ID] PACKAGE [PACKAGE...]");
+        pw.println("    Suspends the specified package(s) (as user).");
         pw.println("");
-        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("  unsuspend [--user USER_ID] PACKAGE [PACKAGE...]");
+        pw.println("    Unsuspends the specified package(s) (as user).");
         pw.println("");
         pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
         pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fc88f39..0b21487 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -171,7 +171,7 @@
     static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
 
     @VisibleForTesting
-    static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
+    static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = Integer.MAX_VALUE;
 
     @VisibleForTesting
     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
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 a0e252a..8075bdb 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
@@ -284,7 +284,8 @@
             int state) throws NameNotFoundException {
         if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
             if (state != DomainVerificationState.STATE_SUCCESS) {
-                return DomainVerificationManager.ERROR_INVALID_STATE_CODE;
+                throw new IllegalArgumentException(
+                        "Caller is not allowed to set state code " + state);
             }
         }
 
@@ -1119,7 +1120,7 @@
             @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
             @Nullable Integer userIdForFilter) throws NameNotFoundException {
         if (domainSetId == null) {
-            return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL);
+            throw new IllegalArgumentException("domainSetId cannot be null");
         }
 
         DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
@@ -1140,9 +1141,9 @@
         }
 
         if (CollectionUtils.isEmpty(domains)) {
-            return GetAttachedResult.error(
-                    DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY);
+            throw new IllegalArgumentException("Provided domain set cannot be empty");
         }
+
         AndroidPackage pkg = pkgSetting.getPkg();
         ArraySet<String> declaredDomains = forAutoVerify
                 ? mCollector.collectValidAutoVerifyDomains(pkg)
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index beebb31..fe21201 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -21,11 +21,13 @@
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.IntentSender;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.hardware.boot.V1_0.IBootControl;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.IRecoverySystem;
 import android.os.IRecoverySystemProgressListener;
 import android.os.PowerManager;
@@ -52,6 +54,7 @@
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -87,6 +90,12 @@
 
     private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
 
+    static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
+    static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
+
+    static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp";
+    static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count";
+
     private final Injector mInjector;
     private final Context mContext;
 
@@ -127,7 +136,7 @@
      */
     @IntDef({ ROR_NEED_PREPARATION,
             ROR_SKIP_PREPARATION_AND_NOTIFY,
-            ROR_SKIP_PREPARATION_NOT_NOTIFY })
+            ROR_SKIP_PREPARATION_NOT_NOTIFY})
     private @interface ResumeOnRebootActionsOnRequest {}
 
     /**
@@ -139,7 +148,7 @@
     private @interface ResumeOnRebootActionsOnClear {}
 
     /**
-     * The error code for reboots initiated by resume on reboot clients.
+     * The error codes for reboots initiated by resume on reboot clients.
      */
     private static final int REBOOT_ERROR_NONE = 0;
     private static final int REBOOT_ERROR_UNKNOWN = 1;
@@ -156,11 +165,64 @@
             REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE})
     private @interface ResumeOnRebootRebootErrorCode {}
 
+    /**
+     * Manages shared preference, i.e. the storage used for metrics reporting.
+     */
+    public static class PreferencesManager {
+        private static final String METRICS_DIR = "recovery_system";
+        private static final String METRICS_PREFS_FILE = "RecoverySystemMetricsPrefs.xml";
+
+        protected final SharedPreferences mSharedPreferences;
+        private final File mMetricsPrefsFile;
+
+        PreferencesManager(Context context) {
+            File prefsDir = new File(Environment.getDataSystemCeDirectory(USER_SYSTEM),
+                    METRICS_DIR);
+            mMetricsPrefsFile = new File(prefsDir, METRICS_PREFS_FILE);
+            mSharedPreferences = context.getSharedPreferences(mMetricsPrefsFile, 0);
+        }
+
+        /** Reads the value of a given key with type long. **/
+        public long getLong(String key, long defaultValue) {
+            return mSharedPreferences.getLong(key, defaultValue);
+        }
+
+        /** Reads the value of a given key with type int. **/
+        public int getInt(String key, int defaultValue) {
+            return mSharedPreferences.getInt(key, defaultValue);
+        }
+
+        /** Stores the value of a given key with type long. **/
+        public void putLong(String key, long value) {
+            mSharedPreferences.edit().putLong(key, value).commit();
+        }
+
+        /** Stores the value of a given key with type int. **/
+        public void putInt(String key, int value) {
+            mSharedPreferences.edit().putInt(key, value).commit();
+        }
+
+        /** Increments the value of a given key with type int. **/
+        public synchronized void incrementIntKey(String key, int defaultInitialValue) {
+            int oldValue = getInt(key, defaultInitialValue);
+            putInt(key, oldValue + 1);
+        }
+
+        /** Delete the preference file and cleanup all metrics storage. **/
+        public void deletePrefsFile() {
+            if (!mMetricsPrefsFile.delete()) {
+                Slog.w(TAG, "Failed to delete metrics prefs");
+            }
+        }
+    }
+
     static class Injector {
         protected final Context mContext;
+        protected final PreferencesManager mPrefs;
 
         Injector(Context context) {
             mContext = context;
+            mPrefs = new PreferencesManager(context);
         }
 
         public Context getContext() {
@@ -236,6 +298,14 @@
             return -1;
         }
 
+        public PreferencesManager getMetricsPrefs() {
+            return mPrefs;
+        }
+
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
         public void reportRebootEscrowPreparationMetrics(int uid,
                 @ResumeOnRebootActionsOnRequest int requestResult, int requestedClientCount) {
             FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_PREPARATION_REPORTED, uid,
@@ -414,7 +484,7 @@
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
                 != PackageManager.PERMISSION_GRANTED
                 && mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT)
-                        != PackageManager.PERMISSION_GRANTED) {
+                    != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller must have " + android.Manifest.permission.RECOVERY
                     + " or " + android.Manifest.permission.REBOOT + " for resume on reboot.");
         }
@@ -427,6 +497,12 @@
             pendingRequestCount = mCallerPendingRequest.size();
         }
 
+        // Save the timestamp and request count for new ror request
+        PreferencesManager prefs = mInjector.getMetricsPrefs();
+        prefs.putLong(packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX,
+                mInjector.getCurrentTimeMillis());
+        prefs.incrementIntKey(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, 0);
+
         mInjector.reportRebootEscrowPreparationMetrics(uid, requestResult, pendingRequestCount);
     }
 
@@ -486,15 +562,31 @@
     }
 
     private void reportMetricsOnPreparedForReboot() {
+        long currentTimestamp = mInjector.getCurrentTimeMillis();
+
         List<String> preparedClients;
         synchronized (this) {
             preparedClients = new ArrayList<>(mCallerPreparedForReboot);
         }
 
+        // Save the timestamp & lskf capture count for lskf capture
+        PreferencesManager prefs = mInjector.getMetricsPrefs();
+        prefs.putLong(LSKF_CAPTURED_TIMESTAMP_PREF, currentTimestamp);
+        prefs.incrementIntKey(LSKF_CAPTURED_COUNT_PREF, 0);
+
         for (String packageName : preparedClients) {
             int uid = mInjector.getUidFromPackageName(packageName);
+
+            int durationSeconds = -1;
+            long requestLskfTimestamp = prefs.getLong(
+                    packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, -1);
+            if (requestLskfTimestamp != -1 && currentTimestamp > requestLskfTimestamp) {
+                durationSeconds = (int) (currentTimestamp - requestLskfTimestamp) / 1000;
+            }
+            Slog.i(TAG, String.format("Reporting lskf captured, lskf capture takes %d seconds for"
+                    + " package %s", durationSeconds, packageName));
             mInjector.reportRebootEscrowLskfCapturedMetrics(uid, preparedClients.size(),
-                    -1 /* duration */);
+                    durationSeconds);
         }
     }
 
@@ -541,6 +633,7 @@
             Slog.w(TAG, "Missing packageName when clearing lskf.");
             return false;
         }
+        // TODO(179105110) Clear the RoR metrics for the given packageName.
 
         @ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName);
         switch (action) {
@@ -641,7 +734,15 @@
             return REBOOT_ERROR_SLOT_MISMATCH;
         }
 
-        if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+        final long origId = Binder.clearCallingIdentity();
+        boolean result;
+        try {
+            result = mInjector.getLockSettingsService().armRebootEscrow();
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        if (!result) {
             Slog.w(TAG, "Failure to escrow key for reboot");
             return REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE;
         }
@@ -649,20 +750,42 @@
         return REBOOT_ERROR_NONE;
     }
 
+    private boolean useServerBasedRoR() {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
+                    "server_based_ror_enabled", false);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     private void reportMetricsOnRebootWithLskf(String packageName, boolean slotSwitch,
             @ResumeOnRebootRebootErrorCode int errorCode) {
         int uid = mInjector.getUidFromPackageName(packageName);
-        boolean serverBased = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
-                "server_based_ror_enabled", false);
+        boolean serverBased = useServerBasedRoR();
         int preparedClientCount;
         synchronized (this) {
             preparedClientCount = mCallerPreparedForReboot.size();
         }
 
-        // TODO(b/179105110) report the true value of duration and counts
+        long currentTimestamp = mInjector.getCurrentTimeMillis();
+        int durationSeconds = -1;
+        PreferencesManager prefs = mInjector.getMetricsPrefs();
+        long lskfCapturedTimestamp = prefs.getLong(LSKF_CAPTURED_TIMESTAMP_PREF, -1);
+        if (lskfCapturedTimestamp != -1 && currentTimestamp > lskfCapturedTimestamp) {
+            durationSeconds = (int) (currentTimestamp - lskfCapturedTimestamp) / 1000;
+        }
+
+        int requestCount = prefs.getInt(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, -1);
+        int lskfCapturedCount = prefs.getInt(LSKF_CAPTURED_COUNT_PREF, -1);
+
+        Slog.i(TAG, String.format("Reporting reboot with lskf, package name %s, client count %d,"
+                        + " request count %d, lskf captured count %d, duration since lskf captured"
+                        + " %d seconds.", packageName, preparedClientCount, requestCount,
+                lskfCapturedCount, durationSeconds));
         mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount,
-                1 /* request count */, slotSwitch, serverBased,
-                -1 /* duration */, 1 /* lskf capture count */);
+                requestCount, slotSwitch, serverBased, durationSeconds, lskfCapturedCount);
     }
 
     private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) {
@@ -673,6 +796,9 @@
             return false;
         }
 
+        // Clear the metrics prefs after a successful RoR reboot.
+        mInjector.getMetricsPrefs().deletePrefsFile();
+
         PowerManager pm = mInjector.getPowerManager();
         pm.reboot(reason);
         return true;
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 23b5d98..030bbd2 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -176,7 +176,7 @@
             if (this == o) {
                 return true;
             }
-            if (o == null || getClass() != o.getClass()) {
+            if (!(o instanceof BoundServiceInfo)) {
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index 7757a7a..e718ba3 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -147,13 +147,23 @@
 
     @Override
     public String toString() {
-        return mServiceConnection.getBoundServiceInfo().toString();
+        MyServiceConnection serviceConnection;
+        synchronized (this) {
+            serviceConnection = mServiceConnection;
+        }
+
+        return serviceConnection.getBoundServiceInfo().toString();
     }
 
     @Override
     public void dump(PrintWriter pw) {
-        pw.println("target service=" + mServiceConnection.getBoundServiceInfo());
-        pw.println("connected=" + mServiceConnection.isConnected());
+        MyServiceConnection serviceConnection;
+        synchronized (this) {
+            serviceConnection = mServiceConnection;
+        }
+
+        pw.println("target service=" + serviceConnection.getBoundServiceInfo());
+        pw.println("connected=" + serviceConnection.isConnected());
     }
 
     // runs on the handler thread, and expects most of it's methods to be called from that thread
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index a0e2286..0b11b0b 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -265,6 +265,10 @@
         synchronized (mSessionsLock) {
             int ioBlockedCounter = mUidsBlockedOnIo.get(uid, 0);
             if (ioBlockedCounter == 0) {
+                Slog.w(TAG, "Unexpected app IO resumption for uid: " + uid);
+            }
+
+            if (ioBlockedCounter <= 1) {
                 mUidsBlockedOnIo.remove(uid);
             } else {
                 mUidsBlockedOnIo.put(uid, --ioBlockedCounter);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index eefa045a..27e2ee5 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -228,7 +228,7 @@
     private void enforceSuggestExternalTimePermission() {
         // We don't expect a call from system server, so simply enforce calling permission.
         mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME,
+                android.Manifest.permission.SUGGEST_EXTERNAL_TIME,
                 "suggest time from external source");
     }
 
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3f74938..89ed956 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -41,6 +41,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -110,6 +111,24 @@
     @NonNull private final VcnNetworkRequestListener mRequestListener;
     @NonNull private final VcnCallback mVcnCallback;
 
+    /**
+     * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
+     *
+     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
+     * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
+     * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
+     *
+     * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
+     * there is potential for a orphaned VcnGatewayConnection instance that does not get properly
+     * shut down.
+     *
+     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
+     * map once they have finished tearing down, which is reported to this VCN via {@link
+     * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
+     * the NetworkProvider so that another VcnGatewayConnectionConfig can match the
+     * previously-matched request.
+     */
+    // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
     @NonNull
     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
             new HashMap<>();
@@ -191,6 +210,19 @@
         return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
     }
 
+    /** Get current Configs and Gateways for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
+            getVcnGatewayConnectionConfigMap() {
+        return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
+    }
+
+    /** Set whether this Vcn is active for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setIsActive(boolean isActive) {
+        mIsActive.set(isActive);
+    }
+
     private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
         @Override
         public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
@@ -202,11 +234,6 @@
 
     @Override
     public void handleMessage(@NonNull Message msg) {
-        // Ignore if this Vcn is not active and we're not receiving new configs
-        if (!isActive() && msg.what != MSG_EVENT_CONFIG_UPDATED) {
-            return;
-        }
-
         switch (msg.what) {
             case MSG_EVENT_CONFIG_UPDATED:
                 handleConfigUpdated((VcnConfig) msg.obj);
@@ -237,9 +264,31 @@
 
         mConfig = config;
 
-        // TODO(b/181815405): Reevaluate active VcnGatewayConnection(s)
+        if (mIsActive.getAndSet(true)) {
+            // VCN is already active - teardown any GatewayConnections whose configs have been
+            // removed and get all current requests
+            for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
+                    mVcnGatewayConnections.entrySet()) {
+                final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
+                final VcnGatewayConnection gatewayConnection = entry.getValue();
 
-        if (!mIsActive.getAndSet(true)) {
+                // GatewayConnectionConfigs must match exactly (otherwise authentication or
+                // connection details may have changed).
+                if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
+                    if (gatewayConnection == null) {
+                        Slog.wtf(
+                                getLogTag(),
+                                "Found gatewayConnectionConfig without GatewayConnection");
+                    } else {
+                        gatewayConnection.teardownAsynchronously();
+                    }
+                }
+            }
+
+            // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
+            // satisfied start a new GatewayConnection)
+            mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+        } else {
             // If this VCN was not previously active, it is exiting Safe Mode. Re-register the
             // request listener to get NetworkRequests again (and all cached requests).
             mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
@@ -259,13 +308,16 @@
     private void handleEnterSafeMode() {
         handleTeardown();
 
-        mVcnGatewayConnections.clear();
-
         mVcnCallback.onEnteredSafeMode();
     }
 
     private void handleNetworkRequested(
             @NonNull NetworkRequest request, int score, int providerId) {
+        if (!isActive()) {
+            Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now");
+            return;
+        }
+
         if (score > getNetworkScore()) {
             if (VDBG) {
                 Slog.v(
@@ -318,8 +370,10 @@
         mVcnGatewayConnections.remove(config);
 
         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
-        // start a new GatewayConnection)
-        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+        // start a new GatewayConnection), but only if the Vcn is still active
+        if (isActive()) {
+            mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+        }
     }
 
     private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
new file mode 100644
index 0000000..953837a
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+import android.util.Range;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
+final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {
+
+    /**
+     * Adapts a sequence of {@link VibrationEffectSegment} to device's absolute frequency values
+     * and respective supported amplitudes.
+     *
+     * <p>This adapter preserves the segment count.
+     */
+    interface AmplitudeFrequencyAdapter {
+        List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
+                VibratorInfo info);
+    }
+
+    private final AmplitudeFrequencyAdapter mAmplitudeFrequencyAdapter;
+
+    DeviceVibrationEffectAdapter() {
+        this(new ClippingAmplitudeFrequencyAdapter());
+    }
+
+    DeviceVibrationEffectAdapter(AmplitudeFrequencyAdapter amplitudeFrequencyAdapter) {
+        mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
+    }
+
+    @Override
+    public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
+        if (!(effect instanceof VibrationEffect.Composed)) {
+            return effect;
+        }
+
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        List<VibrationEffectSegment> mappedSegments = mAmplitudeFrequencyAdapter.apply(
+                composed.getSegments(), info);
+
+        // TODO(b/167947076): add ramp to step adapter once PWLE capability is introduced
+        // TODO(b/167947076): add filter that removes unsupported primitives
+        // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+
+        return new VibrationEffect.Composed(mappedSegments, composed.getRepeatIndex());
+    }
+
+    /**
+     * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
+     * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+     *
+     * <p>Devices with no frequency control will collapse all frequencies to zero and leave
+     * amplitudes unchanged.
+     */
+    private static final class ClippingAmplitudeFrequencyAdapter
+            implements AmplitudeFrequencyAdapter {
+        @Override
+        public List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
+                VibratorInfo info) {
+            List<VibrationEffectSegment> result = new ArrayList<>();
+            int segmentCount = segments.size();
+            for (int i = 0; i < segmentCount; i++) {
+                VibrationEffectSegment segment = segments.get(i);
+                if (segment instanceof StepSegment) {
+                    result.add(apply((StepSegment) segment, info));
+                } else if (segment instanceof RampSegment) {
+                    result.add(apply((RampSegment) segment, info));
+                } else {
+                    result.add(segment);
+                }
+            }
+            return result;
+        }
+
+        private StepSegment apply(StepSegment segment, VibratorInfo info) {
+            float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency());
+            return new StepSegment(
+                    MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)),
+                    info.getAbsoluteFrequency(clampedFrequency),
+                    (int) segment.getDuration());
+        }
+
+        private RampSegment apply(RampSegment segment, VibratorInfo info) {
+            Range<Float> frequencyRange = info.getFrequencyRange();
+            float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency());
+            float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency());
+            return new RampSegment(
+                    MathUtils.min(segment.getStartAmplitude(),
+                            info.getMaxAmplitude(clampedStartFrequency)),
+                    MathUtils.min(segment.getEndAmplitude(),
+                            info.getMaxAmplitude(clampedEndFrequency)),
+                    info.getAbsoluteFrequency(clampedStartFrequency),
+                    info.getAbsoluteFrequency(clampedEndFrequency),
+                    (int) segment.getDuration());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 1e897ea..cd84058 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -24,6 +24,7 @@
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.SparseArray;
@@ -345,6 +346,8 @@
             final long token = proto.start(fieldId);
             if (segment instanceof StepSegment) {
                 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
+            } else if (segment instanceof RampSegment) {
+                dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment);
             } else if (segment instanceof PrebakedSegment) {
                 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
             } else if (segment instanceof PrimitiveSegment) {
@@ -357,6 +360,17 @@
             final long token = proto.start(fieldId);
             proto.write(StepSegmentProto.DURATION, segment.getDuration());
             proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
+            proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency());
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) {
+            final long token = proto.start(fieldId);
+            proto.write(RampSegmentProto.DURATION, segment.getDuration());
+            proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude());
+            proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude());
+            proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency());
+            proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency());
             proto.end(token);
         }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
new file mode 100644
index 0000000..d287c8f
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.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.server.vibrator;
+
+import android.os.VibrationEffect;
+
+/** Function that applies a generic modifier to a {@link VibrationEffect}. */
+interface VibrationEffectModifier<T> {
+
+    /** Applies the modifier to given {@link VibrationEffect}. */
+    VibrationEffect apply(VibrationEffect effect, T modifier);
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 18063de..3090e6d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -27,9 +27,11 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
+import android.os.VibratorInfo;
 import android.os.WorkSource;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
@@ -91,6 +93,8 @@
     private final WorkSource mWorkSource = new WorkSource();
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
+    private final VibrationEffectModifier<VibratorInfo> mDeviceEffectAdapter =
+            new DeviceVibrationEffectAdapter();
     private final Vibration mVibration;
     private final VibrationCallbacks mCallbacks;
     private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -628,6 +632,11 @@
         }
 
         private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+            // TODO(b/167947076): split this into 4 different step implementations:
+            // VibratorPerformStep, VibratorComposePrimitiveStep, VibratorComposePwleStep and
+            // VibratorAmplitudeStep.
+            // Make sure each step carries over the full VibrationEffect and an incremental segment
+            // index, and triggers a final VibratorOffStep once all segments are done.
             VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
             VibrationEffectSegment firstSegment = composed.getSegments().get(0);
             final long duration;
@@ -672,6 +681,28 @@
                     nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
                             controller));
                 }
+            } else if (firstSegment instanceof RampSegment) {
+                int segmentCount = composed.getSegments().size();
+                RampSegment[] primitives = new RampSegment[segmentCount];
+                for (int i = 0; i < segmentCount; i++) {
+                    VibrationEffectSegment segment = composed.getSegments().get(i);
+                    if (segment instanceof RampSegment) {
+                        primitives[i] = (RampSegment) segment;
+                    } else if (segment instanceof StepSegment) {
+                        StepSegment stepSegment = (StepSegment) segment;
+                        primitives[i] = new RampSegment(
+                                stepSegment.getAmplitude(), stepSegment.getAmplitude(),
+                                stepSegment.getFrequency(), stepSegment.getFrequency(),
+                                (int) stepSegment.getDuration());
+                    } else {
+                        primitives[i] = new RampSegment(0, 0, 0, 0, 0);
+                    }
+                }
+                duration = controller.on(primitives, mVibration.id);
+                if (duration > 0) {
+                    nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+                            controller));
+                }
             } else {
                 duration = 0;
             }
@@ -851,7 +882,9 @@
             mVibratorIds = new int[mVibrators.size()];
             for (int i = 0; i < mVibrators.size(); i++) {
                 int vibratorId = mVibrators.keyAt(i);
-                mVibratorEffects.put(vibratorId, mono.getEffect());
+                VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
+                VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo);
+                mVibratorEffects.put(vibratorId, effect);
                 mVibratorIds[i] = vibratorId;
             }
             mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
@@ -863,7 +896,10 @@
             for (int i = 0; i < stereoEffects.size(); i++) {
                 int vibratorId = stereoEffects.keyAt(i);
                 if (mVibrators.contains(vibratorId)) {
-                    mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
+                    VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
+                    VibrationEffect effect = mDeviceEffectAdapter.apply(
+                            stereoEffects.valueAt(i), vibratorInfo);
+                    mVibratorEffects.put(vibratorId, effect);
                 }
             }
             mVibratorIds = new int[mVibratorEffects.size()];
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 66200b3..c897702 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -25,6 +25,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -65,9 +66,12 @@
         mNativeWrapper = nativeWrapper;
         mNativeWrapper.init(vibratorId, listener);
 
+        // TODO(b/167947076): load supported ones from HAL once API introduced
+        VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
+                Float.NaN, nativeWrapper.getResonantFrequency(), Float.NaN, Float.NaN, null);
         mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(),
                 nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(),
-                nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor());
+                nativeWrapper.getQFactor(), frequencyMapping);
     }
 
     /** Register state listener for this vibrator. */
@@ -233,6 +237,19 @@
         }
     }
 
+    /**
+     * Plays a composition of pwle primitives, using {@code vibrationId} or completion callback
+     * to {@link OnVibrationCompleteListener}.
+     *
+     * <p>This will affect the state of {@link #isVibrating()}.
+     *
+     * @return The duration of the effect playing, or 0 if unsupported.
+     */
+    public long on(RampSegment[] primitives, long vibrationId) {
+        // TODO(b/167947076): forward to the HAL once APIs are introduced
+        return 0;
+    }
+
     /** Turns off the vibrator.This will affect the state of {@link #isVibrating()}. */
     public void off() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
new file mode 100644
index 0000000..054044b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.IBinder;
+
+/**
+ * Class needed to expose some {@link ActivityRecord} fields in order to provide
+ * {@link android.service.voice.VoiceInteractionSession#onHandleAssist(AssistState)}
+ *
+ * @hide
+ */
+public class ActivityAssistInfo {
+    private final IBinder mActivityToken;
+    private final IBinder mAssistToken;
+    private final int mTaskId;
+
+    public ActivityAssistInfo(ActivityRecord activityRecord) {
+        this.mActivityToken = activityRecord.appToken;
+        this.mAssistToken = activityRecord.assistToken;
+        this.mTaskId = activityRecord.getTask().mTaskId;
+    }
+
+    /** @hide */
+    public IBinder getActivityToken() {
+        return mActivityToken;
+    }
+
+    /** @hide */
+    public IBinder getAssistToken() {
+        return mAssistToken;
+    }
+
+    /** @hide */
+    public int getTaskId() {
+        return mTaskId;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 12c67bb..c09136e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -32,7 +32,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.voice.IVoiceInteractionSession;
-import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
 import android.window.TaskSnapshot;
 
@@ -166,7 +165,7 @@
      * Returns the top activity from each of the currently visible root tasks, and the related task
      * id. The first entry will be the focused activity.
      */
-    public abstract List<Pair<IBinder, Integer>> getTopVisibleActivities();
+    public abstract List<ActivityAssistInfo> getTopVisibleActivities();
 
     /**
      * Returns whether {@code uid} has any resumed activity.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 29c5cec..c8fa50c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -215,7 +215,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -5112,7 +5111,7 @@
         }
 
         @Override
-        public List<Pair<IBinder, Integer>> getTopVisibleActivities() {
+        public List<ActivityAssistInfo> getTopVisibleActivities() {
             synchronized (mGlobalLock) {
                 return mRootWindowContainer.getTopVisibleActivities();
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6072a06..a9d33dc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3724,10 +3724,15 @@
         // config. (Only happens when the target window is in a different root DA)
         if (target != null) {
             RootDisplayArea targetRoot = target.getRootDisplayArea();
-            if (targetRoot != null) {
+            if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) {
                 // Reposition the IME container to the target root to get the correct bounds and
                 // config.
                 targetRoot.placeImeContainer(mImeWindowsContainer);
+                // Directly hide the IME window so it doesn't flash immediately after reparenting.
+                // InsetsController will make IME visible again before animating it.
+                if (mInputMethodWindow != null) {
+                    mInputMethodWindow.hide(false /* doAnimation */, false /* requestAnim */);
+                }
             }
         }
         // 2. Assign window layers based on the IME surface parent to make sure it is on top of the
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d81181d..20c0d41 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1573,6 +1573,10 @@
                 } else if (document || trIsDocument) {
                     // Only one of these is a document. Not the droid we're looking for.
                     continue;
+                } else if (multiTasksAllowed) {
+                    // Neither is a document, but the new task supports multiple tasks so keep the
+                    // existing task
+                    continue;
                 }
             }
             return i;
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index 505af05..cd20c82 100644
--- a/services/core/java/com/android/server/wm/RootDisplayArea.java
+++ b/services/core/java/com/android/server/wm/RootDisplayArea.java
@@ -79,10 +79,6 @@
      */
     void placeImeContainer(DisplayArea.Tokens imeContainer) {
         final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea();
-        if (previousRoot == this) {
-            // No need to reparent if IME container is below the same root.
-            return;
-        }
 
         List<Feature> features = mFeatures;
         for (int i = 0; i < features.size(); i++) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 857217f..7261048 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1817,8 +1817,8 @@
      * @return a list of pairs, containing activities and their task id which are the top ones in
      * each visible root task. The first entry will be the focused activity.
      */
-    List<Pair<IBinder, Integer>> getTopVisibleActivities() {
-        final ArrayList<Pair<IBinder, Integer>> topVisibleActivities = new ArrayList<>();
+    List<ActivityAssistInfo> getTopVisibleActivities() {
+        final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>();
         final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
         // Traverse all displays.
         forAllRootTasks(rootTask -> {
@@ -1826,8 +1826,7 @@
             if (rootTask.shouldBeVisible(null /* starting */)) {
                 final ActivityRecord top = rootTask.getTopNonFinishingActivity();
                 if (top != null) {
-                    Pair<IBinder, Integer> visibleActivity = new Pair<>(top.appToken,
-                            top.getTask().mTaskId);
+                    ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top);
                     if (rootTask == topFocusedRootTask) {
                         topVisibleActivities.add(0, visibleActivity);
                     } else {
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index a65adbb..8b08314 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -33,6 +33,7 @@
 import android.util.ArrayMap;
 import android.view.View;
 import android.view.WindowManager.LayoutParams.WindowType;
+import android.window.WindowContext;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -40,22 +41,21 @@
 import java.util.Objects;
 
 /**
- * A controller to register/unregister {@link WindowContainerListener} for
- * {@link android.app.WindowContext}.
+ * A controller to register/unregister {@link WindowContainerListener} for {@link WindowContext}.
  *
  * <ul>
- *   <li>When a {@link android.app.WindowContext} is created, it registers the listener via
+ *   <li>When a {@link WindowContext} is created, it registers the listener via
  *     {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)}
  *     automatically.</li>
- *   <li>When the {@link android.app.WindowContext} adds the first window to the screen via
+ *   <li>When the {@link WindowContext} adds the first window to the screen via
  *     {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)},
  *     {@link WindowManagerService} then updates the {@link WindowContextListenerImpl} to listen
  *     to corresponding {@link WindowToken} via this controller.</li>
- *   <li>When the {@link android.app.WindowContext} is GCed, it unregisters the previously
+ *   <li>When the {@link WindowContext} is GCed, it unregisters the previously
  *     registered listener via
  *     {@link WindowManagerService#unregisterWindowContextListener(IBinder)}.
  *     {@link WindowManagerService} is also responsible for removing the
- *     {@link android.app.WindowContext} created {@link WindowToken}.</li>
+ *     {@link WindowContext} created {@link WindowToken}.</li>
  * </ul>
  * <p>Note that the listener may be removed earlier than the
  * {@link #unregisterWindowContainerListener(IBinder)} if the listened {@link WindowContainer} was
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6b1071c..a8ca5b6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2235,11 +2235,6 @@
                 win.mPendingPositionChanged = null;
             }
 
-            if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
-                win.prepareDrawHandlers();
-                result |= RELAYOUT_RES_BLAST_SYNC;
-            }
-
             int attrChanges = 0;
             int flagChanges = 0;
             int privateFlagChanges = 0;
@@ -2512,6 +2507,12 @@
             }
             win.mInRelayout = false;
 
+            if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
+                win.prepareDrawHandlers();
+                win.markRedrawForSyncReported();
+                result |= RELAYOUT_RES_BLAST_SYNC;
+            }
+
             if (configChanged) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                         "relayoutWindow: postNewConfigurationToHandler");
@@ -2707,7 +2708,7 @@
     }
 
     /**
-     * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration
+     * Registers a listener for a {@link android.window.WindowContext} to subscribe to configuration
      * changes of a {@link DisplayArea}.
      *
      * @param clientToken the window context's token
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6da350b..48d4fc5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -763,6 +763,56 @@
      * into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in
      * mReadyDrawHandlers corresponds to state which was observed by the client and we can
      * invoke the consumers.
+     *
+     * To see in more detail that this works, we can look at it like this:
+     *
+     * The client is in one of these states:
+     *
+     * 1. Asleep
+     * 2. Traversal scheduled
+     * 3. Starting traversal
+     * 4. In relayout
+     * 5. Already drawing
+     *
+     * The property we want to implement with the draw handlers is:
+     *   If WM code makes a change to client observable state (e.g. configuration),
+     *   and registers a draw handler (without releasing the WM lock in between),
+     *   the FIRST frame reflecting that change, will be in the Transaction passed
+     *   to the draw handler.
+     *
+     * We describe the expected sequencing in each of the possible client states.
+     * We aim to "prove" that the WM can call applyWithNextDraw() with the client
+     * starting in any state, and achieve the desired result.
+     *
+     * 1. Asleep: The client will wake up in response to MSG_RESIZED, call relayout,
+     * observe the changes. Relayout will return BLAST_SYNC, and the client will
+     * send the transaction to finishDrawing. Since the client was asleep. This
+     * will be the first finishDrawing reflecting the change.
+     * 2, 3: traversal scheduled/starting traversal: These two states can be considered
+     *    together. Each has two sub-states:
+     *       a) Traversal will call relayout. In this case we proceed like the starting
+     *          from asleep case.
+     *       b) Traversal will not call relayout. In this case, the client produced
+     *       frame will not include the change. Because there is no call to relayout
+     *       there is no call to prepareDrawHandlers() and even if the client calls
+     *       finish drawing the draw handler will not be invoked. We have to wait
+     *       on the client to receive MSG_RESIZED, and will sync on the next frame
+     * 4. In relayout. In this case we are careful to prepare draw handlers and check
+     *    whether to return the BLAST flag at the end of relayoutWindow. This means if you
+     *    add a draw handler while the client is in relayout, BLAST_SYNC will be
+     *    immediately returned, and the client will submit the frame corresponding
+     *    to what returns from layout. When we prepare the draw handlers we clear the
+     *    flag which would later cause us to report draw for sync. Since we reported
+     *    sync through relayout (by luck the client was calling relayout perhaps)
+     *    there is no need for a MSG_RESIZED.
+     * 5. Already drawing. This works much like cases 2 and 3. If there is no call to
+     *    finishDrawing then of course the draw handlers will not be invoked and we just
+     *    wait on the next frame for sync. If there is a call to finishDrawing,
+     *    the draw handler will not have been prepared (since we did not call relayout)
+     *    and we will have to wait on the next frame.
+     *
+     * By this logic we can see no matter which of the client states we are in when the
+     * draw handler is added, it will always execute on the expected frame.
      */
     private final List<Consumer<SurfaceControl.Transaction>> mPendingDrawHandlers
         = new ArrayList<>();
@@ -3706,7 +3756,7 @@
         final int displayId = getDisplayId();
         fillClientWindowFrames(mClientWindowFrames);
 
-        mRedrawForSyncReported = true;
+        markRedrawForSyncReported();
 
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mergedConfiguration, forceRelayout,
@@ -5850,7 +5900,7 @@
      * "in relayout", the results may be undefined but at all other times the function
      * should sort of transparently work like this:
      *    1. Make changes to WM hierarchy (say change app configuration)
-     *    2. Call apply with next draw.
+     *    2. Call applyWithNextDraw
      *    3. After finishDrawing, our consumer will be passed the Transaction
      *    containing the buffer, and we can merge in additional operations.
      * See {@link WindowState#mPendingDrawHandlers}
@@ -5879,16 +5929,26 @@
      * See {@link WindowState#mPendingDrawHandlers}
      */
     boolean executeDrawHandlers(SurfaceControl.Transaction t) {
-        if (t == null) t = mTmpTransaction;
         boolean hadHandlers = false;
+        boolean applyHere = false;
+        if (t == null) {
+            t = mTmpTransaction;
+            applyHere = true;
+        }
+
         for (int i = 0; i < mReadyDrawHandlers.size(); i++) {
             mReadyDrawHandlers.get(i).accept(t);
             hadHandlers = true;
         }
-        mReadyDrawHandlers.clear();
-        mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
 
-        t.apply();
+        if (hadHandlers) {
+            mReadyDrawHandlers.clear();
+            mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+        }
+
+        if (applyHere) {
+            t.apply();
+        }
 
         return hadHandlers;
     }
@@ -5906,4 +5966,8 @@
     @WindowManager.LayoutParams.WindowType int getWindowType() {
         return mAttrs.type;
     }
+
+    void markRedrawForSyncReported() {
+       mRedrawForSyncReported = true;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0c80f86..3ef7ccd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -272,14 +272,15 @@
             }
             mDrawState = COMMIT_DRAW_PENDING;
             layoutNeeded = true;
+        }
 
-            if (postDrawTransaction != null) {
+        if (postDrawTransaction != null) {
+            if (mLastHidden) {
                 mPostDrawTransaction.merge(postDrawTransaction);
+                layoutNeeded = true;
+            } else {
+                postDrawTransaction.apply();
             }
-        } else if (postDrawTransaction != null) {
-            // If draw state is not pending we may delay applying this transaction from the client,
-            // so apply it now.
-            postDrawTransaction.apply();
         }
 
         return layoutNeeded;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5163a43..d54cf5f 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -54,6 +54,7 @@
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.window.WindowContext;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
@@ -109,7 +110,7 @@
     private FixedRotationTransformState mFixedRotationTransformState;
 
     /**
-     * When set to {@code true}, this window token is created from {@link android.app.WindowContext}
+     * When set to {@code true}, this window token is created from {@link WindowContext}
      */
     private final boolean mFromClientToken;
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ca56e9b..09ae8fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -102,8 +102,8 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
-// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_ENTERPRISE;
-// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -3089,13 +3089,13 @@
         updatePermissionPolicyCache(userId);
         updateAdminCanGrantSensorsPermissionCache(userId);
 
-        boolean enableEnterpriseNetworkSlice = true;
+        boolean enableEnterpriseNetworkPreferenceEnabled = true;
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
-            enableEnterpriseNetworkSlice = owner != null
+            enableEnterpriseNetworkPreferenceEnabled = owner != null
                     ? owner.mEnterpriseNetworkPreferenceEnabled : true;
         }
-        updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
+        updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkPreferenceEnabled);
 
         startOwnerService(userId, "start-user");
     }
@@ -17009,18 +17009,18 @@
         }
     }
 
-    private void updateNetworkPreferenceForUser(int userId, boolean enableEnterprise) {
+    private void updateNetworkPreferenceForUser(int userId,
+            boolean enableEnterpriseNetworkPreferenceEnabled) {
         if (!isManagedProfile(userId)) {
             return;
         }
-        // TODO(b/178655595)
-        // int networkPreference = enable ? ConnectivityManager.USER_PREFERENCE_ENTERPRISE :
-        //        ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT;
-        // mInjector.binderWithCleanCallingIdentity(() ->
-        //         mInjector.getConnectivityManager().setNetworkPreferenceForUser(
-        //                 UserHandle.of(userId),
-        //                 networkPreference,
-        //                 null /* executor */, null /* listener */));
+        int networkPreference = enableEnterpriseNetworkPreferenceEnabled
+                ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getConnectivityManager().setProfileNetworkPreference(
+                        UserHandle.of(userId),
+                        networkPreference,
+                        null /* executor */, null /* listener */));
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
index fa6ef00..5f35a26 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
@@ -149,7 +149,8 @@
                         .setLocalOnly(true)
                         .setContentIntent(pendingDialogIntent)
                         .setColor(mContext.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
+                                com.android.internal.R.color.system_notification_accent_color))
+                        .extend(new Notification.TvExtender());
 
         if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
             builder.setContentTitle(mContext.getString(
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
index 338659d..bf4e9616 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,19 +44,20 @@
                                         PathCharsLess());
 }
 
-static void preparePathComponent(std::string_view& path, bool trimFront) {
-    if (trimFront) {
-        while (!path.empty() && path.front() == '/') {
-            path.remove_prefix(1);
-        }
+static void preparePathComponent(std::string_view& path, bool trimAll) {
+    // need to check for double front slash as a single one has a separate meaning in front
+    while (!path.empty() && path.front() == '/' &&
+           (trimAll || (path.size() > 1 && path[1] == '/'))) {
+        path.remove_prefix(1);
     }
-    while (!path.empty() && path.back() == '/') {
+    // for the back we don't care about double-vs-single slash difference
+    while (path.size() > !trimAll && path.back() == '/') {
         path.remove_suffix(1);
     }
 }
 
 void details::append_next_path(std::string& target, std::string_view path) {
-    preparePathComponent(path, true);
+    preparePathComponent(path, !target.empty());
     if (path.empty()) {
         return;
     }
diff --git a/services/incremental/path.h b/services/incremental/path.h
index 3e5fd21..e12e1d0 100644
--- a/services/incremental/path.h
+++ b/services/incremental/path.h
@@ -89,15 +89,25 @@
 bool startsWith(std::string_view path, std::string_view prefix);
 
 template <class... Paths>
-std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
-    std::string result;
+std::string join(std::string&& first, std::string_view second, Paths&&... paths) {
+    std::string& result = first;
     {
         using std::size;
         result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
     }
-    result.assign(first);
-    (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
-    return result;
+    (details::append_next_path(result, second), ...,
+     details::append_next_path(result, std::forward<Paths>(paths)));
+    return std::move(result);
+}
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+    return path::join(std::string(), first, second, std::forward<Paths>(paths)...);
+}
+
+template <class... Paths>
+std::string join(const char* first, std::string_view second, Paths&&... paths) {
+    return path::join(std::string_view(first), second, std::forward<Paths>(paths)...);
 }
 
 } // namespace android::incremental::path
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
index cbe479e1..4a8ae5b 100644
--- a/services/incremental/test/path_test.cpp
+++ b/services/incremental/test/path_test.cpp
@@ -40,4 +40,24 @@
     EXPECT_TRUE(!PathLess()("/a/b", "/a"));
 }
 
+TEST(Path, Join) {
+    EXPECT_STREQ("", path::join("", "").c_str());
+
+    EXPECT_STREQ("/", path::join("", "/").c_str());
+    EXPECT_STREQ("/", path::join("/", "").c_str());
+    EXPECT_STREQ("/", path::join("/", "/").c_str());
+    EXPECT_STREQ("/", path::join("/"s, "/").c_str());
+    EXPECT_STREQ("/", path::join("/"sv, "/").c_str());
+    EXPECT_STREQ("/", path::join("/", "/", "/", "/", "/", "/", "/", "/", "/", "/").c_str());
+
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b/"s, "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b/", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b/", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("", "", "/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join(""s, "", "/", "//a/b//", "c", "d").c_str());
+    EXPECT_STREQ("/a/b/c/d", path::join("/a/b", "", "", "/", "", "/", "/", "/c", "d").c_str());
+}
+
 } // namespace android::incremental::path
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
index 8ae4c5a..14c02d5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
 
 import com.android.server.pm.verify.domain.DomainVerificationService;
 
@@ -45,4 +46,16 @@
             throws PackageManager.NameNotFoundException {
         return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId);
     }
+
+    static int setStatusForceNullable(@NonNull DomainVerificationManager manager,
+            @Nullable UUID domainSetId, @Nullable Set<String> domains, int state)
+            throws PackageManager.NameNotFoundException {
+        return manager.setDomainVerificationStatus(domainSetId, domains, state);
+    }
+
+    static int setUserSelectionForceNullable(@NonNull DomainVerificationManager manager,
+            @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled)
+            throws PackageManager.NameNotFoundException {
+        return manager.setDomainVerificationUserSelection(domainSetId, domains, enabled);
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index ef79b08..881604f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.test.verify.domain
 
+import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.parsing.component.ParsedActivity
@@ -23,6 +24,7 @@
 import android.content.pm.verify.domain.DomainVerificationInfo
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.verify.domain.DomainVerificationUserState
+import android.content.pm.verify.domain.IDomainVerificationManager
 import android.os.Build
 import android.os.PatternMatcher
 import android.os.Process
@@ -127,15 +129,17 @@
         assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100))
                 .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
 
-        assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null,
-                setOf(DOMAIN_1), 1100))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setStatusForceNullable(service, null, setOf(DOMAIN_1), 1100)
+        }
 
-        assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null,
-                1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, 1100)
+        }
 
-        assertThat(service.setStatus(UUID_ONE, emptySet(), 1100))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+        assertFailsWith(IllegalArgumentException::class) {
+            service.setStatus(UUID_ONE, emptySet(), 1100)
+        }
 
         assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100))
                 .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
@@ -143,8 +147,9 @@
         assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100))
                 .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
 
-        assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15))
-                .isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE)
+        assertFailsWith(IllegalArgumentException::class) {
+            service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)
+        }
 
         map.clear()
         assertFailsWith(PackageManager.NameNotFoundException::class){
@@ -198,15 +203,19 @@
         assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0))
                 .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
 
-        assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
-                setOf(DOMAIN_1), true, 0))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
+                setOf(DOMAIN_1), true, 0)
+        }
 
-        assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
-                true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
+                true, 0)
+        }
 
-        assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0))
-                .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+        assertFailsWith(IllegalArgumentException::class) {
+            service.setUserSelection(UUID_ONE, emptySet(), true, 0)
+        }
 
         assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0))
                 .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
@@ -297,6 +306,48 @@
         assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
     }
 
+    @Test
+    fun appProcessManager() {
+        // The app side DomainVerificationManager also has to do some argument enforcement since
+        // the input values are transformed before they are sent across Binder. Verify that here.
+
+        // Mock nothing to ensure no calls are made before failing
+        val context = mockThrowOnUnmocked<Context>()
+        val binderInterface = mockThrowOnUnmocked<IDomainVerificationManager>()
+
+        val manager = DomainVerificationManager(context, binderInterface)
+
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setStatusForceNullable(manager, null, setOf(DOMAIN_1), 1100)
+        }
+
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setStatusForceNullable(manager, UUID_ONE, null, 1100)
+        }
+
+        assertFailsWith(IllegalArgumentException::class) {
+            manager.setDomainVerificationStatus(UUID_ONE, emptySet(), 1100)
+        }
+
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(
+                manager, null,
+                setOf(DOMAIN_1), true
+            )
+        }
+
+        assertFailsWith(IllegalArgumentException::class) {
+            DomainVerificationJavaUtil.setUserSelectionForceNullable(
+                manager, UUID_ONE,
+                null, true
+            )
+        }
+
+        assertFailsWith(IllegalArgumentException::class) {
+            manager.setDomainVerificationUserSelection(UUID_ONE, emptySet(), true)
+        }
+    }
+
     private fun makeService(vararg pkgSettings: PackageSetting) =
             makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index ee1a4f4..4bab8e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -5416,9 +5416,8 @@
         inOrder.verify(mJobSchedulerService,
                 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
                 .onControllerStateChanged();
-        // Top and bg EJs should still be allowed to run since they started before the app ran
-        // out of quota.
-        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
+        // Top should still be "in quota" since it started before the app ran on top out of quota.
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
         assertFalse(
                 jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
@@ -5467,7 +5466,7 @@
         // wasn't started. Top should remain in quota since it started when the app was in TOP.
         assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
         assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
-        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
         trackJobs(jobBg2);
         assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
         assertFalse(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 17c6b6f..db0c3ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -36,6 +36,7 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.os.incremental.IncrementalManager
+import android.provider.DeviceConfig
 import android.util.ArrayMap
 import android.util.DisplayMetrics
 import android.util.EventLog
@@ -131,6 +132,7 @@
                 .mockStatic(LockGuard::class.java)
                 .mockStatic(EventLog::class.java)
                 .mockStatic(LocalServices::class.java)
+                .mockStatic(DeviceConfig::class.java)
                 .apply(withSession)
         session = apply.startMocking()
         whenever(mocks.settings.insertPackageSettingLPw(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index a0e208f..46487ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -17,8 +17,13 @@
 package com.android.server.pm
 
 import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
 import com.android.server.apphibernation.AppHibernationManagerInternal
+import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.testutils.whenever
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -33,7 +38,10 @@
 
     companion object {
         val TEST_PACKAGE_NAME = "test.package"
+        val TEST_PACKAGE_2_NAME = "test.package2"
         val TEST_USER_ID = 0
+
+        val KEY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"
     }
 
     @Rule
@@ -47,6 +55,8 @@
     @Throws(Exception::class)
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        wheneverStatic { DeviceConfig.getBoolean(
+            NAMESPACE_APP_HIBERNATION, KEY_APP_HIBERNATION_ENABLED, false) }.thenReturn(true)
         rule.system().stageNominalSystemState()
         whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
             .thenReturn(appHibernationManager)
@@ -68,6 +78,28 @@
         verify(appHibernationManager).setHibernatingGlobally(TEST_PACKAGE_NAME, false)
     }
 
+    @Test
+    fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
+        rule.system().stageScanExistingPackage(
+            TEST_PACKAGE_NAME,
+            1L,
+            rule.system().dataAppDirectory,
+            withPackage = { it.apply { isHasCode = true } })
+        rule.system().stageScanExistingPackage(
+            TEST_PACKAGE_2_NAME,
+            1L,
+            rule.system().dataAppDirectory,
+            withPackage = { it.apply { isHasCode = true } })
+        val pm = createPackageManagerService()
+        rule.system().validateFinalState()
+        whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true)
+
+        val optimizablePkgs = pm.optimizablePackages
+
+        assertTrue(optimizablePkgs.contains(TEST_PACKAGE_NAME))
+        assertFalse(optimizablePkgs.contains(TEST_PACKAGE_2_NAME))
+    }
+
     private fun createPackageManagerService(): PackageManagerService {
         return PackageManagerService(rule.mocks().injector,
             false /*coreOnly*/,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
new file mode 100644
index 0000000..7a6110b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.SparseArray
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
+import com.android.server.utils.WatchedArrayMap
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class SuspendPackagesBroadcastTest {
+
+    companion object {
+        const val TEST_PACKAGE_1 = "com.android.test.package1"
+        const val TEST_PACKAGE_2 = "com.android.test.package2"
+        const val TEST_USER_ID = 0
+    }
+
+    lateinit var pms: PackageManagerService
+    lateinit var packageSetting1: PackageSetting
+    lateinit var packageSetting2: PackageSetting
+    lateinit var packagesToSuspend: Array<String>
+    lateinit var uidsToSuspend: IntArray
+
+    @Captor
+    lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+
+    @Rule
+    @JvmField
+    val rule = MockSystemRule()
+
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        rule.system().stageNominalSystemState()
+        pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
+        packageSetting1 = pms.getPackageSetting(TEST_PACKAGE_1)!!
+        packageSetting2 = pms.getPackageSetting(TEST_PACKAGE_2)!!
+        packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
+
+        pms.sendPackagesSuspendedForUser(
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+        verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+        assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        assertThat(changedUids).asList().containsExactly(
+                packageSetting1.appId, packageSetting2.appId)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
+
+        pms.sendPackagesSuspendedForUser(
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+        bundleCaptor.allValues.forEach {
+            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+            assertThat(changedPackages?.size).isEqualTo(1)
+            assertThat(changedUids?.size).isEqualTo(1)
+            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+        }
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, null)
+
+        pms.sendPackagesSuspendedForUser(
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+        bundleCaptor.allValues.forEach {
+            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+            assertThat(changedPackages?.size).isEqualTo(1)
+            assertThat(changedUids?.size).isEqualTo(1)
+            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+        }
+    }
+
+    private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
+        this.put(TEST_USER_ID, uids)
+    }
+
+    private fun mockAllowList(pkgSetting: PackageSetting, list: SparseArray<IntArray>?) {
+        whenever(rule.mocks().injector.appsFilter.getVisibilityAllowList(eq(pkgSetting),
+                any(IntArray::class.java), any() as WatchedArrayMap<String, PackageSetting>))
+                .thenReturn(list)
+    }
+
+    private fun createPackageManagerService(vararg stageExistingPackages: String):
+            PackageManagerService {
+        stageExistingPackages.forEach {
+            rule.system().stageScanExistingPackage(it, 1L,
+                    rule.system().dataAppDirectory)
+        }
+        var pms = PackageManagerService(rule.mocks().injector,
+                false /*coreOnly*/,
+                false /*factoryTest*/,
+                MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+                false /*isEngBuild*/,
+                false /*isUserDebugBuild*/,
+                Build.VERSION_CODES.CUR_DEVELOPMENT,
+                Build.VERSION.INCREMENTAL)
+        rule.system().validateFinalState()
+        return pms
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index 3d0895d..24a8b61 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -49,14 +49,18 @@
     private static final int USER_ID_2 = 1002;
 
     // Stolen from ConnectivityServiceTest.MockContext
-    class MockContext extends ContextWrapper {
+    static class MockContext extends ContextWrapper {
         private static final String TAG = "MockContext";
 
         // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
         private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
 
+        @Mock
+        private final MockPackageManager mMockPackageManager;
+
         MockContext(Context base) {
             super(base);
+            mMockPackageManager = new MockPackageManager();
         }
 
         /**
@@ -101,6 +105,11 @@
                 throw new SecurityException("[Test] permission denied: " + permission);
             }
         }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mMockPackageManager;
+        }
     }
 
     @Mock
diff --git a/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java
new file mode 100644
index 0000000..5edbc16
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.app;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+public class MockPackageManager extends PackageManager {
+    private static final String TAG = "MockPackageManager";
+
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
+
+    public MockPackageManager() {
+        // Mock the ApplicationInfo, so we can treat the test as a "game".
+        mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(@NonNull String packageName, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Override
+    public PackageInfo getPackageInfoAsUser(@NonNull String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Override
+    public String[] currentToCanonicalPackageNames(@NonNull String[] packageNames) {
+        return new String[0];
+    }
+
+    @Override
+    public String[] canonicalToCurrentPackageNames(@NonNull String[] packageNames) {
+        return new String[0];
+    }
+
+    @Nullable
+    @Override
+    public Intent getLaunchIntentForPackage(@NonNull String packageName) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Intent getCarLaunchIntentForPackage(@NonNull String packageName) {
+        return null;
+    }
+
+    @Override
+    public int[] getPackageGids(@NonNull String packageName) throws NameNotFoundException {
+        return new int[0];
+    }
+
+    @Override
+    public int[] getPackageGids(@NonNull String packageName, int flags)
+            throws NameNotFoundException {
+        return new int[0];
+    }
+
+    @Override
+    public int getPackageUid(@NonNull String packageName, int flags)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public int getPackageUidAsUser(@NonNull String packageName, int userId)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public int getPackageUidAsUser(@NonNull String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(@NonNull String permName, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionInfo> queryPermissionsByGroup(@NonNull String permissionGroup,
+            int flags) throws NameNotFoundException {
+        return null;
+    }
+
+    @Override
+    public boolean arePermissionsIndividuallyControlled() {
+        return false;
+    }
+
+    @Override
+    public boolean isWirelessConsentModeEnabled() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(@NonNull String groupName, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public ApplicationInfo getApplicationInfo(@NonNull String packageName, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, int flags,
+            int userId) throws NameNotFoundException {
+        return mApplicationInfo;
+    }
+
+    @NonNull
+    @Override
+    public ActivityInfo getActivityInfo(@NonNull ComponentName component, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public ServiceInfo getServiceInfo(@NonNull ComponentName component, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<PackageInfo> getInstalledPackages(int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<PackageInfo> getPackagesHoldingPermissions(@NonNull String[] permissions,
+            int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return null;
+    }
+
+    @Override
+    public int checkPermission(@NonNull String permName, @NonNull String packageName) {
+        return 0;
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(@NonNull String permName,
+            @NonNull String packageName) {
+        return false;
+    }
+
+    @Override
+    public boolean addPermission(@NonNull PermissionInfo info) {
+        return false;
+    }
+
+    @Override
+    public boolean addPermissionAsync(@NonNull PermissionInfo info) {
+        return false;
+    }
+
+    @Override
+    public void removePermission(@NonNull String permName) {
+
+    }
+
+    @Override
+    public void grantRuntimePermission(@NonNull String packageName, @NonNull String permName,
+            @NonNull UserHandle user) {
+
+    }
+
+    @Override
+    public void revokeRuntimePermission(@NonNull String packageName, @NonNull String permName,
+            @NonNull UserHandle user) {
+
+    }
+
+    @Override
+    public int getPermissionFlags(@NonNull String permName, @NonNull String packageName,
+            @NonNull UserHandle user) {
+        return 0;
+    }
+
+    @Override
+    public void updatePermissionFlags(@NonNull String permName, @NonNull String packageName,
+            int flagMask, int flagValues, @NonNull UserHandle user) {
+
+    }
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(@NonNull String permName) {
+        return false;
+    }
+
+    @Override
+    public int checkSignatures(@NonNull String packageName1, @NonNull String packageName2) {
+        return 0;
+    }
+
+    @Override
+    public int checkSignatures(int uid1, int uid2) {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public String[] getPackagesForUid(int uid) {
+        return new String[0];
+    }
+
+    @Nullable
+    @Override
+    public String getNameForUid(int uid) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String[] getNamesForUids(int[] uids) {
+        return new String[0];
+    }
+
+    @Override
+    public int getUidForSharedUser(@NonNull String sharedUserName)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public List<ApplicationInfo> getInstalledApplications(int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<InstantAppInfo> getInstantApps() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getInstantAppIcon(String packageName) {
+        return null;
+    }
+
+    @Override
+    public boolean isInstantApp() {
+        return false;
+    }
+
+    @Override
+    public boolean isInstantApp(@NonNull String packageName) {
+        return false;
+    }
+
+    @Override
+    public int getInstantAppCookieMaxBytes() {
+        return 0;
+    }
+
+    @Override
+    public int getInstantAppCookieMaxSize() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public byte[] getInstantAppCookie() {
+        return new byte[0];
+    }
+
+    @Override
+    public void clearInstantAppCookie() {
+
+    }
+
+    @Override
+    public void updateInstantAppCookie(@Nullable byte[] cookie) {
+
+    }
+
+    @Override
+    public boolean setInstantAppCookie(@Nullable byte[] cookie) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public String[] getSystemSharedLibraryNames() {
+        return new String[0];
+    }
+
+    @NonNull
+    @Override
+    public List<SharedLibraryInfo> getSharedLibraries(int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public String getServicesSystemSharedLibraryPackageName() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public String getSharedSystemSharedLibraryPackageName() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ChangedPackages getChangedPackages(int sequenceNumber) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public FeatureInfo[] getSystemAvailableFeatures() {
+        return new FeatureInfo[0];
+    }
+
+    @Override
+    public boolean hasSystemFeature(@NonNull String featureName) {
+        return false;
+    }
+
+    @Override
+    public boolean hasSystemFeature(@NonNull String featureName, int version) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public ResolveInfo resolveActivity(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ResolveInfo resolveActivityAsUser(@NonNull Intent intent, int flags, int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, int flags,
+            int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
+            @Nullable Intent[] specifics, @NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, int flags,
+            int userId) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ResolveInfo resolveService(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ResolveInfo resolveServiceAsUser(@NonNull Intent intent, int flags, int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentServices(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, int flags,
+            int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+            int flags, int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent, int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ProviderInfo resolveContentProvider(@NonNull String authority, int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(@NonNull String providerName, int flags,
+            int userId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<ProviderInfo> queryContentProviders(@Nullable String processName, int uid,
+            int flags) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className,
+            int flags) throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<InstrumentationInfo> queryInstrumentation(@NonNull String targetPackage,
+            int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getDrawable(@NonNull String packageName, int resid,
+            @Nullable ApplicationInfo appInfo) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getActivityIcon(@NonNull ComponentName activityName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getActivityIcon(@NonNull Intent intent) throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getActivityBanner(@NonNull ComponentName activityName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getActivityBanner(@NonNull Intent intent) throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getDefaultActivityIcon() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getApplicationIcon(@NonNull ApplicationInfo info) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getApplicationIcon(@NonNull String packageName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getApplicationBanner(@NonNull ApplicationInfo info) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getApplicationBanner(@NonNull String packageName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getActivityLogo(@NonNull ComponentName activityName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getActivityLogo(@NonNull Intent intent) throws NameNotFoundException {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getApplicationLogo(@NonNull ApplicationInfo info) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getApplicationLogo(@NonNull String packageName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getUserBadgedIcon(@NonNull Drawable drawable, @NonNull UserHandle user) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable getUserBadgedDrawableForDensity(@NonNull Drawable drawable,
+            @NonNull UserHandle user, @Nullable Rect badgeLocation, int badgeDensity) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user, int density) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public CharSequence getUserBadgedLabel(@NonNull CharSequence label,
+            @NonNull UserHandle user) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public CharSequence getText(@NonNull String packageName, int resid,
+            @Nullable ApplicationInfo appInfo) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public XmlResourceParser getXml(@NonNull String packageName, int resid,
+            @Nullable ApplicationInfo appInfo) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public CharSequence getApplicationLabel(@NonNull ApplicationInfo info) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Resources getResourcesForActivity(@NonNull ComponentName activityName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Resources getResourcesForApplication(@NonNull String packageName)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Resources getResourcesForApplicationAsUser(@NonNull String packageName, int userId)
+            throws NameNotFoundException {
+        return null;
+    }
+
+    @Override
+    public int installExistingPackage(@NonNull String packageName)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public int installExistingPackage(@NonNull String packageName, int installReason)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public int installExistingPackageAsUser(@NonNull String packageName, int userId)
+            throws NameNotFoundException {
+        return 0;
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, int verificationCode) {
+
+    }
+
+    @Override
+    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+            long millisecondsToDelay) {
+
+    }
+
+    @Override
+    public void verifyIntentFilter(int verificationId, int verificationCode,
+            @NonNull List<String> failedDomains) {
+
+    }
+
+    @Override
+    public int getIntentVerificationStatusAsUser(@NonNull String packageName, int userId) {
+        return 0;
+    }
+
+    @Override
+    public boolean updateIntentVerificationStatusAsUser(@NonNull String packageName, int status,
+            int userId) {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+            @NonNull String packageName) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<IntentFilter> getAllIntentFilters(@NonNull String packageName) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getDefaultBrowserPackageNameAsUser(int userId) {
+        return null;
+    }
+
+    @Override
+    public boolean setDefaultBrowserPackageNameAsUser(@Nullable String packageName,
+            int userId) {
+        return false;
+    }
+
+    @Override
+    public void setInstallerPackageName(@NonNull String targetPackage,
+            @Nullable String installerPackageName) {
+
+    }
+
+    @Override
+    public void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble) {
+
+    }
+
+    @Override
+    public void deletePackage(@NonNull String packageName,
+            @Nullable IPackageDeleteObserver observer, int flags) {
+
+    }
+
+    @Override
+    public void deletePackageAsUser(@NonNull String packageName,
+            @Nullable IPackageDeleteObserver observer, int flags, int userId) {
+
+    }
+
+    @Nullable
+    @Override
+    public String getInstallerPackageName(@NonNull String packageName) {
+        return null;
+    }
+
+    @Override
+    public void clearApplicationUserData(@NonNull String packageName,
+            @Nullable IPackageDataObserver observer) {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFiles(@NonNull String packageName,
+            @Nullable IPackageDataObserver observer) {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFilesAsUser(@NonNull String packageName, int userId,
+            @Nullable IPackageDataObserver observer) {
+
+    }
+
+    @Override
+    public void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize,
+            @Nullable IPackageDataObserver observer) {
+
+    }
+
+    @Override
+    public void freeStorage(@Nullable String volumeUuid, long freeStorageSize,
+            @Nullable IntentSender pi) {
+
+    }
+
+    @Override
+    public void getPackageSizeInfoAsUser(@NonNull String packageName, int userId,
+            @Nullable IPackageStatsObserver observer) {
+
+    }
+
+    @Override
+    public void addPackageToPreferred(@NonNull String packageName) {
+
+    }
+
+    @Override
+    public void removePackageFromPreferred(@NonNull String packageName) {
+
+    }
+
+    @NonNull
+    @Override
+    public List<PackageInfo> getPreferredPackages(int flags) {
+        return null;
+    }
+
+    @Override
+    public void addPreferredActivity(@NonNull IntentFilter filter, int match,
+            @Nullable ComponentName[] set, @NonNull ComponentName activity) {
+
+    }
+
+    @Override
+    public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
+            @Nullable ComponentName[] set, @NonNull ComponentName activity) {
+
+    }
+
+    @Override
+    public void clearPackagePreferredActivities(@NonNull String packageName) {
+
+    }
+
+    @Override
+    public int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
+            @NonNull List<ComponentName> outActivities, @Nullable String packageName) {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities) {
+        return null;
+    }
+
+    @Override
+    public void setComponentEnabledSetting(@NonNull ComponentName componentName, int newState,
+            int flags) {
+
+    }
+
+    @Override
+    public int getComponentEnabledSetting(@NonNull ComponentName componentName) {
+        return 0;
+    }
+
+    @Override
+    public void setApplicationEnabledSetting(@NonNull String packageName, int newState,
+            int flags) {
+
+    }
+
+    @Override
+    public int getApplicationEnabledSetting(@NonNull String packageName) {
+        return 0;
+    }
+
+    @Override
+    public void flushPackageRestrictionsAsUser(int userId) {
+
+    }
+
+    @Override
+    public boolean setApplicationHiddenSettingAsUser(@NonNull String packageName,
+            boolean hidden, @NonNull UserHandle userHandle) {
+        return false;
+    }
+
+    @Override
+    public boolean getApplicationHiddenSettingAsUser(@NonNull String packageName,
+            @NonNull UserHandle userHandle) {
+        return false;
+    }
+
+    @Override
+    public boolean isSafeMode() {
+        return false;
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(@NonNull OnPermissionsChangedListener listener) {
+
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(
+            @NonNull OnPermissionsChangedListener listener) {
+
+    }
+
+    @NonNull
+    @Override
+    public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public KeySet getSigningKeySet(@NonNull String packageName) {
+        return null;
+    }
+
+    @Override
+    public boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks) {
+        return false;
+    }
+
+    @Override
+    public boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks) {
+        return false;
+    }
+
+    @Override
+    public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+        return false;
+    }
+
+    @Override
+    public void setApplicationCategoryHint(@NonNull String packageName, int categoryHint) {
+
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) {
+        return 0;
+    }
+
+    @Override
+    public void registerMoveCallback(@NonNull MoveCallback callback, @NonNull Handler handler) {
+
+    }
+
+    @Override
+    public void unregisterMoveCallback(@NonNull MoveCallback callback) {
+
+    }
+
+    @Override
+    public int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol) {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<VolumeInfo> getPackageCandidateVolumes(@NonNull ApplicationInfo app) {
+        return null;
+    }
+
+    @Override
+    public int movePrimaryStorage(@NonNull VolumeInfo vol) {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public VolumeInfo getPrimaryStorageCurrentVolume() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+        return null;
+    }
+
+    @Override
+    public boolean isUpgrade() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public PackageInstaller getPackageInstaller() {
+        return null;
+    }
+
+    @Override
+    public void addCrossProfileIntentFilter(@NonNull IntentFilter filter, int sourceUserId,
+            int targetUserId, int flags) {
+
+    }
+
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
+
+    }
+
+    @NonNull
+    @Override
+    public Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo,
+            @Nullable ApplicationInfo appInfo) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo,
+            @Nullable ApplicationInfo appInfo) {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageAvailable(@NonNull String packageName) {
+        return false;
+    }
+
+    @Override
+    public int getInstallReason(@NonNull String packageName, @NonNull UserHandle user) {
+        return 0;
+    }
+
+    @Override
+    public boolean canRequestPackageInstalls() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public ComponentName getInstantAppInstallerComponent() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getInstantAppAndroidId(@NonNull String packageName,
+            @NonNull UserHandle user) {
+        return null;
+    }
+
+    @Override
+    public void registerDexModule(@NonNull String dexModulePath,
+            @Nullable DexModuleRegisterCallback callback) {
+
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 1b8ab21..0dbf3fe 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.intThat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.app.IActivityManager;
@@ -36,6 +37,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.net.Uri;
@@ -53,11 +55,11 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Tests for {@link com.android.server.apphibernation.AppHibernationService}
@@ -81,6 +83,8 @@
     @Mock
     private IPackageManager mIPackageManager;
     @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
     private IActivityManager mIActivityManager;
     @Mock
     private UserManager mUserManager;
@@ -116,8 +120,8 @@
         mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         UserInfo userInfo = addUser(USER_ID_1);
-        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
         doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
 
         mAppHibernationService.mIsServiceEnabled = true;
     }
@@ -150,8 +154,8 @@
             throws RemoteException {
         // WHEN a new user is added and a package from the user is hibernated
         UserInfo user2 = addUser(USER_ID_2);
-        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
         doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
         mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
 
         // THEN the new user's package is hibernated
@@ -188,8 +192,8 @@
         // GIVEN an unlocked user with all packages installed
         UserInfo userInfo =
                 addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
-        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
         doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
 
         // WHEN packages are hibernated for the user
         mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
@@ -254,18 +258,29 @@
         }
 
         @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
         public UserManager getUserManager() {
             return mUserManager;
         }
 
         @Override
+        public Executor getBackgroundExecutor() {
+            // Just execute immediately in tests.
+            return r -> r.run();
+        }
+
+        @Override
         public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
-            return Mockito.mock(HibernationStateDiskStore.class);
+            return mock(HibernationStateDiskStore.class);
         }
 
         @Override
         public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
-            return Mockito.mock(HibernationStateDiskStore.class);
+            return mock(HibernationStateDiskStore.class);
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index ad22cba..f3ee233 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -87,7 +87,8 @@
                 schema1,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // Insert package2 schema
         List<AppSearchSchema> schema2 =
@@ -98,7 +99,8 @@
                 schema2,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // Insert package1 document
         GenericDocument document1 =
@@ -139,7 +141,8 @@
                 schema1,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // Insert package2 schema
         List<AppSearchSchema> schema2 =
@@ -150,7 +153,8 @@
                 schema2,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // Insert package1 document
         GenericDocument document1 =
@@ -208,7 +212,8 @@
                 /*schemasPackageAccessible=*/ ImmutableMap.of(
                         "schema1",
                         ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // "schema1" is platform hidden now and package visible to package1
         assertThat(
@@ -234,7 +239,8 @@
                 /*schemasPackageAccessible=*/ ImmutableMap.of(
                         "schema1",
                         ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // Check that "schema1" still has the same visibility settings
         assertThat(
@@ -283,7 +289,8 @@
                 /*schemasPackageAccessible=*/ ImmutableMap.of(
                         "schema1",
                         ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
 
         // "schema1" is platform hidden now and package accessible
         assertThat(
@@ -305,7 +312,8 @@
                 /*schemas=*/ Collections.emptyList(),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ true);
+                /*forceOverride=*/ true,
+                /*schemaVersion=*/ 0);
 
         // Check that "schema1" is no longer considered platform hidden or package accessible
         assertThat(
@@ -328,7 +336,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
         assertThat(
                         mAppSearchImpl
                                 .getVisibilityStoreLocked()
@@ -351,7 +360,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
         assertThat(
                         mAppSearchImpl
                                 .getVisibilityStoreLocked()
@@ -369,7 +379,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
         assertThat(
                         mAppSearchImpl
                                 .getVisibilityStoreLocked()
@@ -387,7 +398,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
         assertThat(
                         mAppSearchImpl
                                 .getVisibilityStoreLocked()
@@ -416,7 +428,8 @@
                 /*schemasPackageAccessible=*/ ImmutableMap.of(
                         "Schema",
                         ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*schemaVersion=*/ 0);
         assertThat(
                         mAppSearchImpl
                                 .getVisibilityStoreLocked()
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 e0cdedd..c34c00d 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
@@ -26,6 +26,7 @@
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.StorageInfo;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 import android.util.ArrayMap;
@@ -406,7 +407,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert enough documents.
         for (int i = 0;
@@ -471,7 +473,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert document
         GenericDocument document =
@@ -504,14 +507,16 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         mAppSearchImpl.setSchema(
                 "package",
                 "database2",
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert documents
         GenericDocument document1 =
@@ -555,7 +560,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert document
         GenericDocument document =
@@ -597,7 +603,8 @@
                 schema1,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert package2 schema
         List<AppSearchSchema> schema2 =
@@ -608,7 +615,8 @@
                 schema2,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert package1 document
         GenericDocument document =
@@ -647,7 +655,8 @@
                 schema1,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert package2 schema
         List<AppSearchSchema> schema2 =
@@ -658,7 +667,8 @@
                 schema2,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Insert package1 document
         GenericDocument document =
@@ -735,7 +745,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto =
@@ -781,7 +792,8 @@
                 oldSchemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Create incompatible schema
         List<AppSearchSchema> newSchemas =
@@ -795,7 +807,8 @@
                         newSchemas,
                         /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                         /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                        /*forceOverride=*/ true);
+                        /*forceOverride=*/ true,
+                        /*version=*/ 0);
         assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
         assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
     }
@@ -816,7 +829,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto =
@@ -847,7 +861,8 @@
                         finalSchemas,
                         /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                         /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                        /*forceOverride=*/ false);
+                        /*forceOverride=*/ false,
+                        /*version=*/ 0);
 
         // Check the Document type has been deleted.
         assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
@@ -859,7 +874,8 @@
                 finalSchemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ true);
+                /*forceOverride=*/ true,
+                /*version=*/ 0);
 
         // Check Document schema is removed.
         expectedProto =
@@ -895,14 +911,16 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         mAppSearchImpl.setSchema(
                 "package",
                 "database2",
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto =
@@ -940,7 +958,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ true);
+                /*forceOverride=*/ true,
+                /*version=*/ 0);
 
         // Create expected schemaType list, database 1 should only contain Email but database 2
         // remains in same.
@@ -982,7 +1001,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         assertThat(mAppSearchImpl.getPackageToDatabases())
                 .containsExactlyEntriesIn(expectedMapping);
 
@@ -994,7 +1014,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         assertThat(mAppSearchImpl.getPackageToDatabases())
                 .containsExactlyEntriesIn(expectedMapping);
 
@@ -1006,7 +1027,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         assertThat(mAppSearchImpl.getPackageToDatabases())
                 .containsExactlyEntriesIn(expectedMapping);
     }
@@ -1024,7 +1046,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
 
         // Has both databases
@@ -1035,7 +1058,8 @@
                 Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
     }
 
@@ -1077,6 +1101,330 @@
     }
 
     @Test
+    public void testReportUsage() throws Exception {
+        // Insert schema
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two docs
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "uri1", "type").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "uri2", "type").build();
+        mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+
+        // Report some usages. uri1 has 2 app and 1 system usage, uri2 has 1 app and 2 system usage.
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri1",
+                /*usageTimestampMillis=*/ 10,
+                /*systemUsage=*/ false);
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri1",
+                /*usageTimestampMillis=*/ 20,
+                /*systemUsage=*/ false);
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri1",
+                /*usageTimestampMillis=*/ 1000,
+                /*systemUsage=*/ true);
+
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri2",
+                /*usageTimestampMillis=*/ 100,
+                /*systemUsage=*/ false);
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri2",
+                /*usageTimestampMillis=*/ 200,
+                /*systemUsage=*/ true);
+        mAppSearchImpl.reportUsage(
+                "package",
+                "database",
+                "namespace",
+                "uri2",
+                /*usageTimestampMillis=*/ 150,
+                /*systemUsage=*/ true);
+
+        // Sort by app usage count: uri1 should win
+        List<SearchResult> page =
+                mAppSearchImpl
+                        .query(
+                                "package",
+                                "database",
+                                "",
+                                new SearchSpec.Builder()
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
+                                        .build())
+                        .getResults();
+        assertThat(page).hasSize(2);
+        assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+        assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+
+        // Sort by app usage timestamp: uri2 should win
+        page =
+                mAppSearchImpl
+                        .query(
+                                "package",
+                                "database",
+                                "",
+                                new SearchSpec.Builder()
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .setRankingStrategy(
+                                                SearchSpec
+                                                        .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
+                                        .build())
+                        .getResults();
+        assertThat(page).hasSize(2);
+        assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+        assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+
+        // Sort by system usage count: uri2 should win
+        page =
+                mAppSearchImpl
+                        .query(
+                                "package",
+                                "database",
+                                "",
+                                new SearchSpec.Builder()
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .setRankingStrategy(
+                                                SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT)
+                                        .build())
+                        .getResults();
+        assertThat(page).hasSize(2);
+        assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+        assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+
+        // Sort by system usage timestamp: uri1 should win
+        page =
+                mAppSearchImpl
+                        .query(
+                                "package",
+                                "database",
+                                "",
+                                new SearchSpec.Builder()
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .setRankingStrategy(
+                                                SearchSpec
+                                                        .RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP)
+                                        .build())
+                        .getResults();
+        assertThat(page).hasSize(2);
+        assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+        assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+    }
+
+    @Test
+    public void testGetStorageInfoForPackage_nonexistentPackage() throws Exception {
+        // "package2" doesn't exist yet, so it shouldn't have any storage size
+        StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("nonexistent.package");
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetStorageInfoForPackage_withoutDocument() throws Exception {
+        // Insert schema for "package1"
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Since "package1" doesn't have a document, it get any space attributed to it.
+        StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetStorageInfoForPackage_proportionalToDocuments() throws Exception {
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+
+        // Insert schema for "package1"
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert document for "package1"
+        GenericDocument document =
+                new GenericDocument.Builder<>("namespace", "uri1", "type").build();
+        mAppSearchImpl.putDocument("package1", "database", document, /*logger=*/ null);
+
+        // Insert schema for "package2"
+        mAppSearchImpl.setSchema(
+                "package2",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two documents for "package2"
+        document = new GenericDocument.Builder<>("namespace", "uri1", "type").build();
+        mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
+        document = new GenericDocument.Builder<>("namespace", "uri2", "type").build();
+        mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
+
+        StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
+        long size1 = storageInfo.getSizeBytes();
+        assertThat(size1).isGreaterThan(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
+
+        storageInfo = mAppSearchImpl.getStorageInfoForPackage("package2");
+        long size2 = storageInfo.getSizeBytes();
+        assertThat(size2).isGreaterThan(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
+
+        // Size is proportional to number of documents. Since "package2" has twice as many
+        // documents as "package1", its size is twice as much too.
+        assertThat(size2).isAtLeast(2 * size1);
+    }
+
+    @Test
+    public void testGetStorageInfoForDatabase_nonexistentPackage() throws Exception {
+        // "package2" doesn't exist yet, so it shouldn't have any storage size
+        StorageInfo storageInfo =
+                mAppSearchImpl.getStorageInfoForDatabase(
+                        "nonexistent.package", "nonexistentDatabase");
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetStorageInfoForDatabase_nonexistentDatabase() throws Exception {
+        // Insert schema for "package1"
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // "package2" doesn't exist yet, so it shouldn't have any storage size
+        StorageInfo storageInfo =
+                mAppSearchImpl.getStorageInfoForDatabase("package1", "nonexistentDatabase");
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetStorageInfoForDatabase_withoutDocument() throws Exception {
+        // Insert schema for "package1"
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Since "package1", "database1" doesn't have a document, it get any space attributed to it.
+        StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetStorageInfoForDatabase_proportionalToDocuments() throws Exception {
+        // Insert schema for "package1", "database1" and "database2"
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database2",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Add a document for "package1", "database1"
+        GenericDocument document =
+                new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+        mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
+
+        // Add two documents for "package1", "database2"
+        document = new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+        mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
+        document = new GenericDocument.Builder<>("namespace1", "uri2", "type").build();
+        mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
+
+        StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
+        long size1 = storageInfo.getSizeBytes();
+        assertThat(size1).isGreaterThan(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
+
+        storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database2");
+        long size2 = storageInfo.getSizeBytes();
+        assertThat(size2).isGreaterThan(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
+
+        // Size is proportional to number of documents. Since "database2" has twice as many
+        // documents as "database1", its size is twice as much too.
+        assertThat(size2).isAtLeast(2 * size1);
+    }
+
+    @Test
     public void testThrowsExceptionIfClosed() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
         AppSearchImpl appSearchImpl =
@@ -1095,7 +1443,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
 
         appSearchImpl.close();
 
@@ -1109,7 +1458,8 @@
                             schemas,
                             /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                             /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                            /*forceOverride=*/ false);
+                            /*forceOverride=*/ false,
+                            /*version=*/ 0);
                 });
 
         expectThrows(
@@ -1179,7 +1529,8 @@
                             "database",
                             "namespace",
                             "uri",
-                            /*usageTimestampMillis=*/ 1000L);
+                            /*usageTimestampMillis=*/ 1000L,
+                            /*systemUsage=*/ false);
                 });
 
         expectThrows(
@@ -1203,6 +1554,18 @@
         expectThrows(
                 IllegalStateException.class,
                 () -> {
+                    appSearchImpl.getStorageInfoForPackage("package");
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.getStorageInfoForDatabase("package", "database");
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
                     appSearchImpl.persistToDisk();
                 });
     }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index 467ede4..673b7ee 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -127,7 +127,8 @@
                 schemas,
                 /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                 /*schemasPackageAccessible=*/ Collections.emptyMap(),
-                /*forceOverride=*/ false);
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "uri", "type").build();
 
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
index dc225f1..0fe3903 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
@@ -32,7 +32,6 @@
     public void testGetProto_Email() {
         AppSearchSchema emailSchema =
                 new AppSearchSchema.Builder("Email")
-                        .setVersion(12345)
                         .addProperty(
                                 new AppSearchSchema.StringPropertyConfig.Builder("subject")
                                         .setCardinality(
@@ -89,7 +88,7 @@
                                                                 TermMatchType.Code.PREFIX)))
                         .build();
 
-        assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema))
+        assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema, /*version=*/ 12345))
                 .isEqualTo(expectedEmailProto);
         assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedEmailProto))
                 .isEqualTo(emailSchema);
@@ -142,7 +141,9 @@
                                                 PropertyConfigProto.Cardinality.Code.OPTIONAL))
                         .build();
 
-        assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(musicRecordingSchema))
+        assertThat(
+                        SchemaToProtoConverter.toSchemaTypeConfigProto(
+                                musicRecordingSchema, /*version=*/ 0))
                 .isEqualTo(expectedMusicRecordingProto);
         assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedMusicRecordingProto))
                 .isEqualTo(musicRecordingSchema);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 6cdac1a..557c14a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -81,7 +81,7 @@
 
                     @NonNull
                     @Override
-                    public StartUserClient<?> getStartUserClient(int newUserId) {
+                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
                                 TEST_SENSOR_ID, mUserStartedCallback);
                     }
@@ -157,12 +157,12 @@
         }
     }
 
-    private class TestUserStartedCallback implements StartUserClient.UserStartedCallback {
+    private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
 
         int numInvocations;
 
         @Override
-        public void onUserStarted(int newUserId) {
+        public void onUserStarted(int newUserId, Object newObject) {
             numInvocations++;
             mCurrentUserId = newUserId;
         }
@@ -183,8 +183,7 @@
         @Override
         public void start(@NonNull Callback callback) {
             super.start(callback);
-            mUserStoppedCallback.onUserStopped();
-            callback.onClientFinished(this, true /* success */);
+            onUserStopped();
         }
 
         @Override
@@ -193,10 +192,10 @@
         }
     }
 
-    private static class TestStartUserClient extends StartUserClient<Object> {
+    private static class TestStartUserClient extends StartUserClient<Object, Object> {
         public TestStartUserClient(@NonNull Context context,
                 @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
-                int sensorId, @NonNull UserStartedCallback callback) {
+                int sensorId, @NonNull UserStartedCallback<Object> callback) {
             super(context, lazyDaemon, token, userId, sensorId, callback);
         }
 
@@ -208,7 +207,7 @@
         @Override
         public void start(@NonNull Callback callback) {
             super.start(callback);
-            mUserStartedCallback.onUserStarted(getTargetUserId());
+            mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
             callback.onClientFinished(this, true /* success */);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 04a7122..0cd6d86 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,10 +24,12 @@
 
 import android.content.Context;
 import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -56,7 +58,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private FaceProvider mFaceProvider;
+    private TestableFaceProvider mFaceProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -80,7 +82,7 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG,
+        mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG,
                 mLockoutResetDispatcher);
     }
 
@@ -123,4 +125,19 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private static class TestableFaceProvider extends FaceProvider {
+        public TestableFaceProvider(@NonNull Context context,
+                @NonNull SensorProps[] props,
+                @NonNull String halInstanceName,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+            super(context, props, halInstanceName, lockoutResetDispatcher);
+        }
+
+        @Override
+        synchronized IFace getHalInstance() {
+            return mock(IFace.class);
+        }
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index d149880..94cc666 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,10 +24,12 @@
 
 import android.content.Context;
 import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -59,7 +61,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private FingerprintProvider mFingerprintProvider;
+    private TestableFingerprintProvider mFingerprintProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -83,7 +85,7 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG,
+        mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
     }
 
@@ -126,4 +128,20 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private static class TestableFingerprintProvider extends FingerprintProvider {
+        public TestableFingerprintProvider(@NonNull Context context,
+                @NonNull SensorProps[] props,
+                @NonNull String halInstanceName,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            super(context, props, halInstanceName, lockoutResetDispatcher,
+                    gestureAvailabilityDispatcher);
+        }
+
+        @Override
+        synchronized IFingerprint getHalInstance() {
+            return mock(IFingerprint.class);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/content/OWNERS b/services/tests/servicestests/src/com/android/server/content/OWNERS
new file mode 100644
index 0000000..6264a14
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/content/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index d093e79..c2a81d9 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.content;
 
+import android.content.ContentResolver;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -57,6 +58,23 @@
                 SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
     }
 
+    public void testSyncExtrasEqualsFails_WithNull() throws Exception {
+        Bundle b1 = new Bundle();
+        b1.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+        b1.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+
+        Bundle b2 = new Bundle();
+        b2.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+        b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+        b2.putString(null, "Hello NPE!");
+        b2.putString("a", "b");
+        b2.putString("c", "d");
+        b2.putString("e", "f");
+
+        assertFalse("Extras not properly compared between bundles.",
+                SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+    }
+
     public void testSyncExtrasEqualsFails_differentValues() throws Exception {
         Bundle b1 = new Bundle();
         Bundle b2 = new Bundle();
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 557d07c..cc206a1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -92,6 +92,7 @@
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -124,9 +125,7 @@
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.internal.util.collections.Sets;
@@ -221,16 +220,6 @@
     private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
     private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
 
-    @BeforeClass
-    public static void setUpClass() {
-        Notification.DevFlags.sForceDefaults = true;
-    }
-
-    @AfterClass
-    public static void tearDownClass() {
-        Notification.DevFlags.sForceDefaults = false;
-    }
-
     @Before
     public void setUp() throws Exception {
 
@@ -4017,23 +4006,27 @@
 
     @Test
     public void testUpdateNetworkPreferenceOnStartOnStopUser() throws Exception {
-        dpms.handleStartUser(CALLER_USER_HANDLE);
-        // TODO(b/178655595)
-        // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
-        //         any(UserHandle.class),
-        //         anyInt(),
-        //         any(Executor.class),
-        //         any(Runnable.class)
-        //);
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
 
-        dpms.handleStopUser(CALLER_USER_HANDLE);
-        // TODO(b/178655595)
-        // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
-        //         any(UserHandle.class),
-        //         eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
-        //         any(Executor.class),
-        //         any(Runnable.class)
-        //);
+        dpms.handleStartUser(managedProfileUserId);
+        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+                eq(UserHandle.of(managedProfileUserId)),
+                anyInt(),
+                any(),
+                any()
+        );
+
+        dpms.handleStopUser(managedProfileUserId);
+        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+                eq(UserHandle.of(managedProfileUserId)),
+                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
+                any(),
+                any()
+        );
     }
 
     @Test
@@ -4044,26 +4037,28 @@
         assertExpectException(SecurityException.class, null,
                 () -> dpm.isEnterpriseNetworkPreferenceEnabled());
 
-        setupProfileOwner();
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
         dpm.setEnterpriseNetworkPreferenceEnabled(false);
         assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse();
-        // TODO(b/178655595)
-        // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
-        //         any(UserHandle.class),
-        //         eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
-        //         any(Executor.class),
-        //         any(Runnable.class)
-        //);
+        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+                eq(UserHandle.of(managedProfileUserId)),
+                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
+                any(),
+                any()
+        );
 
         dpm.setEnterpriseNetworkPreferenceEnabled(true);
         assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue();
-        // TODO(b/178655595)
-        // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
-        //         any(UserHandle.class),
-        //         eq(ConnectivityManager.USER_PREFERENCE_ENTERPRISE),
-        //         any(Executor.class),
-        //         any(Runnable.class)
-        //);
+        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+                eq(UserHandle.of(managedProfileUserId)),
+                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE),
+                any(),
+                any()
+        );
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 50ba761..ee9de07 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -107,7 +107,7 @@
                     }
 
                     @Override
-                    Looper getServiceLooper() {
+                    protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index aa5bc93..d5df071 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -108,7 +108,7 @@
                     }
 
                     @Override
-                    Looper getServiceLooper() {
+                    protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 41f4a1e..8b23be5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -91,7 +91,7 @@
                     }
 
                     @Override
-                    Looper getServiceLooper() {
+                    protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index b3ee18d..ee1a857 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -19,21 +19,35 @@
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK;
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
 
+import static com.android.server.hdmi.Constants.ABORT_REFUSED;
+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_BACKUP_1;
 import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
 import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static com.android.server.hdmi.Constants.HANDLED;
+import static com.android.server.hdmi.Constants.MESSAGE_STANDBY;
+import static com.android.server.hdmi.Constants.NOT_HANDLED;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import android.content.Context;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Binder;
@@ -44,6 +58,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.SystemService;
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 
 import junit.framework.TestCase;
@@ -53,6 +68,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.ArrayList;
 import java.util.Optional;
 
 /** Tests for {@link com.android.server.hdmi.HdmiCecController} class. */
@@ -63,27 +79,7 @@
 
     private FakeNativeWrapper mNativeWrapper;
 
-    private class MyHdmiControlService extends HdmiControlService {
-
-        MyHdmiControlService(Context context) {
-            super(context);
-        }
-
-        @Override
-        Looper getIoLooper() {
-            return mMyLooper;
-        }
-
-        @Override
-        Looper getServiceLooper() {
-            return mMyLooper;
-        }
-
-        @Override
-        int getCecVersion() {
-            return mCecVersion;
-        }
-    }
+    private HdmiControlService mHdmiControlServiceSpy;
 
     private HdmiCecController mHdmiCecController;
     private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
@@ -101,12 +97,39 @@
     @Before
     public void SetUp() {
         mMyLooper = mTestLooper.getLooper();
-        mMyLooper = mTestLooper.getLooper();
-        HdmiControlService hdmiControlService = new MyHdmiControlService(
-                InstrumentationRegistry.getTargetContext());
+
+        mHdmiControlServiceSpy = spy(new HdmiControlService(
+                InstrumentationRegistry.getTargetContext()));
+        doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
+        doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
+        doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
+        doNothing().when(mHdmiControlServiceSpy)
+                .writeStringSystemProperty(anyString(), anyString());
+
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
-                hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
+                mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+    }
+
+    /** Additional setup for tests for onMessage
+     *  Adds a local playback device and allocates addresses
+     */
+    public void setUpForOnMessageTest() {
+        mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+
+        HdmiCecLocalDevicePlayback playbackDevice =
+                new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
+        playbackDevice.init();
+
+        ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
+        localDevices.add(playbackDevice);
+
+        mHdmiControlServiceSpy.initService();
+        mHdmiControlServiceSpy.allocateLogicalAddress(localDevices,
+                HdmiControlService.INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        mTestLooper.dispatchAll();
     }
 
     /** Tests for {@link HdmiCecController#allocateLogicalAddress} */
@@ -119,7 +142,6 @@
 
     @Test
     public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() {
-
         mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
         mTestLooper.dispatchAll();
         assertEquals(ADDR_TV, mLogicalAddress);
@@ -308,4 +330,90 @@
         TestCase.assertEquals(Optional.of(callerUid), uidReadingRunnable.getWorkSourceUid());
         TestCase.assertEquals(runnerUid, Binder.getCallingWorkSourceUid());
     }
+
+    @Test
+    public void onMessage_broadcastMessage_doesNotSendFeatureAbort() {
+        setUpForOnMessageTest();
+
+        doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+        HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+                ADDR_TV, ADDR_BROADCAST);
+
+        mNativeWrapper.onCecMessage(receivedMessage);
+
+        mTestLooper.dispatchAll();
+
+        assertFalse("No <Feature Abort> messages should be sent",
+                mNativeWrapper.getResultMessages().stream().anyMatch(
+                        message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+    }
+
+    @Test
+    public void onMessage_notTheDestination_doesNotSendFeatureAbort() {
+        setUpForOnMessageTest();
+
+        doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+        HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+                ADDR_TV, ADDR_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(receivedMessage);
+
+        mTestLooper.dispatchAll();
+
+        assertFalse("No <Feature Abort> messages should be sent",
+                mNativeWrapper.getResultMessages().stream().anyMatch(
+                        message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+    }
+
+    @Test
+    public void onMessage_handledMessage_doesNotSendFeatureAbort() {
+        setUpForOnMessageTest();
+
+        doReturn(HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+        HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+                ADDR_TV, ADDR_PLAYBACK_1);
+        mNativeWrapper.onCecMessage(receivedMessage);
+
+        mTestLooper.dispatchAll();
+
+        assertFalse("No <Feature Abort> messages should be sent",
+                mNativeWrapper.getResultMessages().stream().anyMatch(
+                        message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+    }
+
+    @Test
+    public void onMessage_unhandledMessage_sendsFeatureAbortUnrecognizedOpcode() {
+        setUpForOnMessageTest();
+
+        doReturn(NOT_HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+        HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+                ADDR_TV, ADDR_PLAYBACK_1);
+        mNativeWrapper.onCecMessage(receivedMessage);
+
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void onMessage_sendsFeatureAbortWithRequestedOperand() {
+        setUpForOnMessageTest();
+
+        doReturn(ABORT_REFUSED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+        HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+                ADDR_TV, ADDR_PLAYBACK_1);
+        mNativeWrapper.onCecMessage(receivedMessage);
+
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 6bb148d..38a44c6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -20,7 +20,6 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
-import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
 
@@ -248,7 +247,8 @@
                         ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true);
         HdmiCecMessage messageGive =
                 HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive))
+            .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -262,45 +262,25 @@
         HdmiCecMessage messageGive =
                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-            .isTrue();
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
 
     @Test
     public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception {
-        HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                        ADDR_AUDIO_SYSTEM,
-                        ADDR_TV,
-                        Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
-                        Constants.ABORT_REFUSED);
-
         mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
-        assertThat(
-            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                 MESSAGE_REQUEST_SAD_LCPM))
-            .isTrue();
-        mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+                .isEqualTo(Constants.ABORT_REFUSED);
     }
 
     @Test
     public void handleRequestShortAudioDescriptor_samOff() throws Exception {
-        HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                        ADDR_AUDIO_SYSTEM,
-                        ADDR_TV,
-                        Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
-                        Constants.ABORT_NOT_IN_CORRECT_MODE);
-
         mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
-        assertThat(
-            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                 MESSAGE_REQUEST_SAD_LCPM))
-            .isEqualTo(true);
-        mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+                .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
     }
 
     // Testing device has sadConfig.xml
@@ -315,10 +295,9 @@
                         Constants.ABORT_UNABLE_TO_DETERMINE);
 
         mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
-        assertThat(
-            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                 MESSAGE_REQUEST_SAD_LCPM))
-            .isEqualTo(true);
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -335,17 +314,18 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-            .isTrue();
+            .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         // Check if correctly turned on
         mNativeWrapper.clearResultMessages();
         expectedMessage =
             HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-            .isTrue();
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isFalse();
@@ -365,7 +345,7 @@
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
-            .isTrue();
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
 
@@ -373,7 +353,7 @@
         expectedMessage =
             HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-            .isTrue();
+            .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isTrue();
@@ -441,7 +421,8 @@
     public void handleActiveSource_updateActiveSource() throws Exception {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
         ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
             .isTrue();
@@ -513,17 +494,10 @@
     public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
-        HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                        ADDR_AUDIO_SYSTEM,
-                        ADDR_TV,
-                        Constants.MESSAGE_REQUEST_ARC_INITIATION,
-                        Constants.ABORT_NOT_IN_CORRECT_MODE);
         mNativeWrapper.setPhysicalAddress(0x1100);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
-        mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+                .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
     }
 
     @Test
@@ -533,7 +507,8 @@
         mNativeWrapper.setPhysicalAddress(0x1000);
         mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
             .isNotEmpty();
@@ -548,7 +523,8 @@
                 HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
         mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
             .isNotEmpty();
@@ -567,7 +543,8 @@
                         Constants.MESSAGE_REQUEST_ARC_TERMINATION,
                         Constants.ABORT_NOT_IN_CORRECT_MODE);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
     }
@@ -576,17 +553,10 @@
     public void handleRequestArcInit_arcIsNotSupported() throws Exception {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
-        HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                        ADDR_AUDIO_SYSTEM,
-                        ADDR_TV,
-                        Constants.MESSAGE_REQUEST_ARC_INITIATION,
-                        Constants.ABORT_UNRECOGNIZED_OPCODE);
         mArcSupport = false;
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
-        mTestLooper.dispatchAll();
-        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+                .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
     }
 
     @Test
@@ -612,7 +582,8 @@
                         Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
                         Constants.ABORT_REFUSED);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
+                .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -629,7 +600,8 @@
         mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
 
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -642,7 +614,8 @@
 
         ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
             .isEqualTo(expectedActiveSource);
@@ -659,7 +632,8 @@
         ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS);
         int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
             .isEqualTo(expectedActiveSource);
@@ -677,7 +651,8 @@
                 HdmiCecMessageBuilder.buildRoutingInformation(
                         ADDR_AUDIO_SYSTEM, HDMI_1_PHYSICAL_ADDRESS);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -691,7 +666,8 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
     }
@@ -727,18 +703,15 @@
         int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
         HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                 ADDR_TV, scaledVolume, mute);
-        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
 
         HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
                 ADDR_AUDIO_SYSTEM);
         mNativeWrapper.clearResultMessages();
-        boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
+                        .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(expected);
-        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
-        assertThat(handled).isTrue();
     }
 
     @Test
@@ -758,18 +731,15 @@
         int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
         HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                 ADDR_TV, scaledVolume, mute);
-        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
 
         HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
                 ADDR_AUDIO_SYSTEM);
         mNativeWrapper.clearResultMessages();
-        boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
+                .isEqualTo(Constants.ABORT_REFUSED);
         mTestLooper.dispatchAll();
 
-        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
-        assertThat(handled).isTrue();
     }
 
     @Test
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 1a6bad8..80da696 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -113,7 +113,7 @@
                     }
 
                     @Override
-                    boolean isStandbyMessageReceived() {
+                    protected boolean isStandbyMessageReceived() {
                         return mStandby;
                     }
 
@@ -184,7 +184,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -205,7 +206,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -226,7 +228,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -247,7 +250,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -270,7 +274,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
@@ -293,7 +298,8 @@
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
@@ -309,7 +315,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 0x5000);
@@ -329,7 +336,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 mPlaybackPhysicalAddress);
@@ -349,7 +357,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 mPlaybackPhysicalAddress);
@@ -368,7 +377,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isTrue();
     }
@@ -383,7 +393,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isFalse();
     }
@@ -399,7 +410,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         assertThat(mStandby).isFalse();
     }
@@ -461,7 +473,8 @@
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
         mStandby = false;
         HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 0x5000);
@@ -481,7 +494,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 mPlaybackPhysicalAddress);
@@ -501,7 +515,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 mPlaybackPhysicalAddress);
@@ -520,7 +535,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isTrue();
     }
@@ -535,7 +551,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isFalse();
     }
@@ -551,7 +568,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
                         mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         assertThat(mStandby).isFalse();
     }
@@ -606,7 +624,8 @@
     public void handleSetStreamPath() {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+                .isEqualTo(Constants.HANDLED);
     }
 
     @Test
@@ -616,7 +635,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
     }
 
@@ -629,7 +649,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, false);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
     }
 
@@ -640,7 +661,8 @@
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
                         Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, true);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
     }
 
@@ -883,7 +905,8 @@
         mStandby = false;
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mStandby).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
@@ -900,7 +923,8 @@
                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE);
         mStandby = false;
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mStandby).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
@@ -918,7 +942,8 @@
         mStandby = false;
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mStandby).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
@@ -931,7 +956,8 @@
                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
         mStandby = false;
         HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mStandby).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
@@ -996,12 +1022,14 @@
         // 1. DUT is <AS>.
         HdmiCecMessage message1 = HdmiCecMessageBuilder.buildActiveSource(
                 mHdmiCecLocalDevicePlayback.mAddress, mPlaybackPhysicalAddress);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         assertThat(mStandby).isFalse();
         // 2. DUT loses <AS> and goes to sleep.
         HdmiCecMessage message2 = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isTrue();
         // 3. DUT becomes <AS> again.
@@ -1271,7 +1299,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
                 0x5000);
@@ -1290,7 +1319,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isTrue();
     }
@@ -1305,7 +1335,8 @@
         mStandby = false;
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
-        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+                .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         assertThat(mStandby).isFalse();
     }
@@ -1492,7 +1523,8 @@
         mHdmiControlService.toggleAndFollowTvPower();
         HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
                 mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
-        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
@@ -1510,7 +1542,8 @@
         mHdmiControlService.toggleAndFollowTvPower();
         HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
                 mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
-        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
@@ -1525,7 +1558,8 @@
         mHdmiControlService.toggleAndFollowTvPower();
         HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
                 mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY);
-        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
@@ -1543,7 +1577,8 @@
         mHdmiControlService.toggleAndFollowTvPower();
         HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
                 mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN);
-        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+                .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index b3f0085..6880302 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -198,8 +198,8 @@
                         ADDR_PLAYBACK_1,
                         Constants.MESSAGE_CEC_VERSION,
                         HdmiCecMessage.EMPTY_PARAM);
-        boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg);
-        assertFalse(handleResult);
+        @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.dispatchMessage(msg);
+        assertEquals(Constants.NOT_HANDLED, handleResult);
     }
 
     @Test
@@ -213,7 +213,7 @@
                     (byte) (DEVICE_TV & 0xFF)
                 };
         callbackResult = -1;
-        boolean handleResult =
+        @Constants.HandleMessageResult int handleResult =
                 mHdmiLocalDevice.handleGivePhysicalAddress(
                         (int finalResult) -> callbackResult = finalResult);
         mTestLooper.dispatchAll();
@@ -221,7 +221,7 @@
          * Test if CecMessage is sent successfully SendMessageResult#SUCCESS is defined in HAL as 0
          */
         assertEquals(0, callbackResult);
-        assertTrue(handleResult);
+        assertEquals(Constants.HANDLED, handleResult);
     }
 
     @Test
@@ -251,85 +251,85 @@
     public void handleUserControlPressed_volumeUp() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
 
-        assertTrue(result);
+        assertEquals(Constants.HANDLED, result);
     }
 
     @Test
     public void handleUserControlPressed_volumeDown() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
 
-        assertTrue(result);
+        assertEquals(Constants.HANDLED, result);
     }
 
     @Test
     public void handleUserControlPressed_volumeMute() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_MUTE));
 
-        assertTrue(result);
+        assertEquals(Constants.HANDLED, result);
     }
 
     @Test
     public void handleUserControlPressed_volumeUp_disabled() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
 
-        assertFalse(result);
+        assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
     }
 
     @Test
     public void handleUserControlPressed_volumeDown_disabled() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
 
-        assertFalse(result);
+        assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
     }
 
     @Test
     public void handleUserControlPressed_volumeMute_disabled() {
         mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
                         HdmiCecKeycode.CEC_KEYCODE_MUTE));
 
-        assertFalse(result);
+        assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
     }
 
     @Test
     public void handleCecVersion_isHandled() {
-        boolean result = mHdmiLocalDevice.onMessage(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.onMessage(
                 HdmiCecMessageBuilder.buildCecVersion(ADDR_PLAYBACK_1, mHdmiLocalDevice.mAddress,
                         HdmiControlManager.HDMI_CEC_VERSION_1_4_B));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
     }
 
     @Test
     public void handleUserControlPressed_power_localDeviceInStandby_shouldTurnOn() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isTrue();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -337,11 +337,11 @@
     @Test
     public void handleUserControlPressed_power_localDeviceOn_shouldNotChangePowerStatus() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isFalse();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -349,11 +349,11 @@
     @Test
     public void handleUserControlPressed_powerToggleFunction_localDeviceInStandby_shouldTurnOn() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isTrue();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -361,11 +361,11 @@
     @Test
     public void handleUserControlPressed_powerToggleFunction_localDeviceOn_shouldTurnOff() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isFalse();
         assertThat(mStandbyMessageReceived).isTrue();
     }
@@ -373,11 +373,11 @@
     @Test
     public void handleUserControlPressed_powerOnFunction_localDeviceInStandby_shouldTurnOn() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isTrue();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -385,11 +385,11 @@
     @Test
     public void handleUserControlPressed_powerOnFunction_localDeviceOn_noPowerStatusChange() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isFalse();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -397,11 +397,11 @@
     @Test
     public void handleUserControlPressed_powerOffFunction_localDeviceStandby_noPowerStatusChange() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isFalse();
         assertThat(mStandbyMessageReceived).isFalse();
     }
@@ -409,11 +409,11 @@
     @Test
     public void handleUserControlPressed_powerOffFunction_localDeviceOn_shouldTurnOff() {
         mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
                 HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
                         HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION));
 
-        assertThat(result).isTrue();
+        assertEquals(Constants.HANDLED, result);
         assertThat(mWakeupMessageReceived).isFalse();
         assertThat(mStandbyMessageReceived).isTrue();
     }
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 4b3ef2f..39e06a3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -233,7 +233,7 @@
         mWokenUp = false;
         HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
                 mTvLogicalAddress);
-        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue();
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
     }
@@ -247,7 +247,7 @@
         mWokenUp = false;
         HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
                 Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
-        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue();
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isTrue();
     }
@@ -261,7 +261,7 @@
         mWokenUp = false;
         HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
                 mTvLogicalAddress);
-        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue();
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isFalse();
     }
@@ -275,7 +275,7 @@
         mWokenUp = false;
         HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
                 Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
-        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue();
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
         assertThat(mWokenUp).isFalse();
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
deleted file mode 100644
index 70718f7..0000000
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.server.hdmi;
-
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.content.Context;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.os.Looper;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Tests for {@link HdmiControlServiceBinderAPITest} class.
- */
-@SmallTest
-@RunWith(JUnit4.class)
-public class HdmiControlServiceBinderAPITest {
-
-    private Context mContext;
-
-    private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice {
-
-        private boolean mCanGoToStandby;
-        private boolean mIsStandby;
-        private boolean mIsDisabled;
-
-        protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) {
-            super(service, deviceType);
-        }
-
-        @Override
-        protected void onAddressAllocated(int logicalAddress, int reason) {
-        }
-
-        @Override
-        protected int getPreferredAddress() {
-            return 0;
-        }
-
-        @Override
-        protected void setPreferredAddress(int addr) {
-        }
-
-        @Override
-        protected boolean canGoToStandby() {
-            return mCanGoToStandby;
-        }
-
-        @Override
-        protected void disableDevice(
-            boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
-            mIsDisabled = true;
-            originalCallback.onCleared(this);
-        }
-
-        @Override
-        protected void onStandby(boolean initiatedByCec, int standbyAction) {
-            mIsStandby = true;
-        }
-
-        protected boolean isStandby() {
-            return mIsStandby;
-        }
-
-        protected boolean isDisabled() {
-            return mIsDisabled;
-        }
-
-        protected void setCanGoToStandby(boolean canGoToStandby) {
-            mCanGoToStandby = canGoToStandby;
-        }
-
-        @Override
-        protected int getRcProfile() {
-            return 0;
-        }
-
-        @Override
-        protected List<Integer> getRcFeatures() {
-            return Collections.emptyList();
-        }
-
-        @Override
-        protected List<Integer> getDeviceFeatures() {
-            return Collections.emptyList();
-        }
-    }
-
-    private static final String TAG = "HdmiControlServiceBinderAPITest";
-    private HdmiControlService mHdmiControlService;
-    private HdmiCecController mHdmiCecController;
-    private HdmiCecLocalDevicePlayback mPlaybackDevice;
-    private FakeNativeWrapper mNativeWrapper;
-    private Looper mMyLooper;
-    private TestLooper mTestLooper = new TestLooper();
-    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
-    private HdmiPortInfo[] mHdmiPortInfo;
-    private int mResult;
-    private int mPowerStatus;
-
-    @Before
-    public void SetUp() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        // Some tests expect no logical addresses being allocated at the beginning of the test.
-        setHdmiControlEnabled(false);
-
-        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContext);
-
-        mHdmiControlService =
-            new HdmiControlService(mContext) {
-                @Override
-                void sendCecCommand(HdmiCecMessage command) {
-                    switch (command.getOpcode()) {
-                        case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
-                            HdmiCecMessage message =
-                                HdmiCecMessageBuilder.buildReportPowerStatus(
-                                    Constants.ADDR_TV,
-                                    Constants.ADDR_PLAYBACK_1,
-                                    HdmiControlManager.POWER_STATUS_ON);
-                            handleCecCommand(message);
-                            break;
-                        default:
-                            return;
-                    }
-                }
-
-                @Override
-                boolean isPowerStandby() {
-                    return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
-                }
-
-                @Override
-                protected HdmiCecConfig getHdmiCecConfig() {
-                    return hdmiCecConfig;
-                }
-            };
-        mMyLooper = mTestLooper.getLooper();
-
-        mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
-            @Override
-            protected void wakeUpIfActiveSource() {}
-
-            @Override
-            protected void setPreferredAddress(int addr) {}
-
-            @Override
-            protected int getPreferredAddress() {
-                return Constants.ADDR_PLAYBACK_1;
-            }
-        };
-        mPlaybackDevice.init();
-
-        mHdmiControlService.setIoLooper(mMyLooper);
-
-        mNativeWrapper = new FakeNativeWrapper();
-        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
-                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
-        mHdmiControlService.setCecController(mHdmiCecController);
-        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
-        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
-
-        mLocalDevices.add(mPlaybackDevice);
-        mHdmiPortInfo = new HdmiPortInfo[1];
-        mHdmiPortInfo[0] =
-            new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
-        mNativeWrapper.setPortInfo(mHdmiPortInfo);
-        mHdmiControlService.initService();
-        mResult = -1;
-        mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-
-        mTestLooper.dispatchAll();
-    }
-
-    @Test
-    public void oneTouchPlay_addressNotAllocated() {
-        assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
-        mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
-            @Override
-            public void onComplete(int result) {
-                mResult = result;
-            }
-        });
-        assertEquals(mResult, -1);
-        assertThat(mPlaybackDevice.isActiveSource()).isFalse();
-
-        setHdmiControlEnabled(true);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-        mTestLooper.dispatchAll();
-        assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
-        assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
-        assertThat(mPlaybackDevice.isActiveSource()).isTrue();
-    }
-
-    @Test
-    public void oneTouchPlay_addressAllocated() {
-        setHdmiControlEnabled(true);
-
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-        mTestLooper.dispatchAll();
-        assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
-        mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
-            @Override
-            public void onComplete(int result) {
-                mResult = result;
-            }
-        });
-        assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
-        assertThat(mPlaybackDevice.isActiveSource()).isTrue();
-    }
-
-    private void setHdmiControlEnabled(boolean enabled) {
-        int value = enabled ? 1 : 0;
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED,
-                value);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index b5336e3..68aa96a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -28,6 +28,9 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -68,7 +71,7 @@
 @RunWith(JUnit4.class)
 public class HdmiControlServiceTest {
 
-    private class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
+    protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
 
         private boolean mCanGoToStandby;
         private boolean mIsStandby;
@@ -118,7 +121,7 @@
             mCanGoToStandby = canGoToStandby;
         }
     }
-    private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
+    protected static class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
 
         private boolean mCanGoToStandby;
         private boolean mIsStandby;
@@ -171,15 +174,14 @@
 
     private static final String TAG = "HdmiControlServiceTest";
     private Context mContextSpy;
-    private HdmiControlService mHdmiControlService;
+    private HdmiControlService mHdmiControlServiceSpy;
     private HdmiCecController mHdmiCecController;
-    private MockAudioSystemDevice mAudioSystemDevice;
-    private MockPlaybackDevice mPlaybackDevice;
+    private MockAudioSystemDevice mAudioSystemDeviceSpy;
+    private MockPlaybackDevice mPlaybackDeviceSpy;
     private FakeNativeWrapper mNativeWrapper;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
-    private boolean mStandbyMessageReceived;
     private HdmiPortInfo[] mHdmiPortInfo;
 
     @Mock private IPowerManager mIPowerManagerMock;
@@ -199,36 +201,32 @@
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
 
-        mHdmiControlService = new HdmiControlService(mContextSpy) {
-            @Override
-            boolean isStandbyMessageReceived() {
-                return mStandbyMessageReceived;
-            }
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy));
+        doNothing().when(mHdmiControlServiceSpy)
+                .writeStringSystemProperty(anyString(), anyString());
 
-            @Override
-            protected void writeStringSystemProperty(String key, String value) {
-            }
-        };
         mMyLooper = mTestLooper.getLooper();
 
-        mAudioSystemDevice = new MockAudioSystemDevice(mHdmiControlService);
-        mPlaybackDevice = new MockPlaybackDevice(mHdmiControlService);
-        mAudioSystemDevice.init();
-        mPlaybackDevice.init();
+        mAudioSystemDeviceSpy = spy(new MockAudioSystemDevice(mHdmiControlServiceSpy));
+        mPlaybackDeviceSpy = spy(new MockPlaybackDevice(mHdmiControlServiceSpy));
+        mAudioSystemDeviceSpy.init();
+        mPlaybackDeviceSpy.init();
 
-        mHdmiControlService.setIoLooper(mMyLooper);
-        mHdmiControlService.setHdmiCecConfig(hdmiCecConfig);
-        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mHdmiControlServiceSpy.setIoLooper(mMyLooper);
+        mHdmiControlServiceSpy.setHdmiCecConfig(hdmiCecConfig);
+        mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
 
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
-                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
-        mHdmiControlService.setCecController(mHdmiCecController);
-        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
-        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+                mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+        mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+        mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create(
+                mHdmiControlServiceSpy));
+        mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator(
+                mHdmiControlServiceSpy));
 
-        mLocalDevices.add(mAudioSystemDevice);
-        mLocalDevices.add(mPlaybackDevice);
+        mLocalDevices.add(mAudioSystemDeviceSpy);
+        mLocalDevices.add(mPlaybackDeviceSpy);
         mHdmiPortInfo = new HdmiPortInfo[4];
         mHdmiPortInfo[0] =
             new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
@@ -239,80 +237,81 @@
         mHdmiPortInfo[3] =
             new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
         mNativeWrapper.setPortInfo(mHdmiPortInfo);
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                 HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        mHdmiControlService.initService();
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.initService();
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
 
         mTestLooper.dispatchAll();
     }
 
     @Test
     public void onStandby_notByCec_cannotGoToStandby() {
-        mStandbyMessageReceived = false;
-        mPlaybackDevice.setCanGoToStandby(false);
+        doReturn(false).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
 
-        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
-        assertTrue(mPlaybackDevice.isStandby());
-        assertTrue(mAudioSystemDevice.isStandby());
-        assertFalse(mPlaybackDevice.isDisabled());
-        assertFalse(mAudioSystemDevice.isDisabled());
+        mPlaybackDeviceSpy.setCanGoToStandby(false);
+
+        mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        assertTrue(mPlaybackDeviceSpy.isStandby());
+        assertTrue(mAudioSystemDeviceSpy.isStandby());
+        assertFalse(mPlaybackDeviceSpy.isDisabled());
+        assertFalse(mAudioSystemDeviceSpy.isDisabled());
     }
 
     @Test
     public void onStandby_byCec() {
-        mStandbyMessageReceived = true;
+        doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
 
-        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
-        assertTrue(mPlaybackDevice.isStandby());
-        assertTrue(mAudioSystemDevice.isStandby());
-        assertTrue(mPlaybackDevice.isDisabled());
-        assertTrue(mAudioSystemDevice.isDisabled());
+        mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        assertTrue(mPlaybackDeviceSpy.isStandby());
+        assertTrue(mAudioSystemDeviceSpy.isStandby());
+        assertTrue(mPlaybackDeviceSpy.isDisabled());
+        assertTrue(mAudioSystemDeviceSpy.isDisabled());
     }
 
     @Test
     public void initialPowerStatus_normalBoot_isTransientToStandby() {
-        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
     }
 
     @Test
     public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException {
         when(mIPowerManagerMock.isInteractive()).thenReturn(false);
-        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
     }
 
     @Test
     public void powerStatusAfterBootComplete_normalBoot_isOn() {
-        mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
-        mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
-        assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED);
+        assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
     }
 
     @Test
     public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException {
         when(mIPowerManagerMock.isInteractive()).thenReturn(false);
-        mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
-        assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+        mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED);
+        assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_STANDBY);
     }
 
     @Test
     public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
 
-        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
 
-        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
 
         HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                 Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
@@ -322,21 +321,21 @@
 
     @Test
     public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
         mTestLooper.dispatchAll();
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
 
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
-        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
 
-        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
@@ -347,53 +346,53 @@
 
     @Test
     public void setAndGetCecVolumeControlEnabled_isApi() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
 
     @Test
     public void setAndGetCecVolumeControlEnabled_changesSetting() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        assertThat(mHdmiControlService.readIntSetting(
+        assertThat(mHdmiControlServiceSpy.readIntSetting(
                 Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.readIntSetting(
+        assertThat(mHdmiControlServiceSpy.readIntSetting(
                 Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
 
     @Test
     public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
 
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
 
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
@@ -401,60 +400,61 @@
     @Test
     public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() {
         int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
-        assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(volumeControlEnabled);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl())
+                .isEqualTo(volumeControlEnabled);
     }
 
     @Test
     public void disableAndReenableCec_volumeControlReturnsToOriginalValue_disabled() {
         int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_DISABLED;
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
     }
 
     @Test
     public void disableAndReenableCec_volumeControlFeatureListenersNotified() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
 
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(callback.mCallbackReceived).isTrue();
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
 
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
 
     @Test
     public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() {
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
 
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
         mTestLooper.dispatchAll();
 
         assertThat(callback.mCallbackReceived).isTrue();
@@ -464,11 +464,11 @@
 
     @Test
     public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() {
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
 
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
         mTestLooper.dispatchAll();
 
         assertThat(callback.mCallbackReceived).isTrue();
@@ -478,13 +478,13 @@
 
     @Test
     public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() {
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
 
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
 
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
         mTestLooper.dispatchAll();
 
@@ -495,15 +495,15 @@
 
     @Test
     public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() {
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
 
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
         mTestLooper.dispatchAll();
 
-        mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback);
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.removeHdmiControlVolumeControlStatusChangeListener(callback);
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
         mTestLooper.dispatchAll();
 
@@ -514,16 +514,16 @@
 
     @Test
     public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() {
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
         VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback();
         VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback();
 
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1);
-        mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback1);
+        mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback2);
 
 
-        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+        mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
         mTestLooper.dispatchAll();
 
@@ -537,47 +537,48 @@
 
     @Test
     public void getCecVersion_1_4() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
     }
 
     @Test
     public void getCecVersion_2_0() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
 
     @Test
     public void getCecVersion_change() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
 
     @Test
     public void handleGiveFeatures_cec14_featureAbort() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -592,11 +593,11 @@
 
     @Test
     public void handleGiveFeatures_cec20_reportsFeatures() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -606,43 +607,43 @@
         HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
                 Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
-                mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
-                mPlaybackDevice.getDeviceFeatures());
+                mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+                mPlaybackDeviceSpy.getDeviceFeatures());
         assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
     }
 
     @Test
     public void initializeCec_14_doesNotBroadcastReportFeatures() {
         mNativeWrapper.clearResultMessages();
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
                 Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
-                mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
-                mPlaybackDevice.getDeviceFeatures());
+                mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+                mPlaybackDeviceSpy.getDeviceFeatures());
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportFeatures);
     }
 
     @Test
     public void initializeCec_20_reportsFeaturesBroadcast() {
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
                 Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
-                mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
-                mPlaybackDevice.getDeviceFeatures());
+                mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+                mPlaybackDeviceSpy.getDeviceFeatures());
         assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
     }
 
@@ -653,7 +654,7 @@
 
         Binder.setCallingWorkSourceUid(callerUid);
         WorkSourceUidReadingRunnable uidReadingRunnable = new WorkSourceUidReadingRunnable();
-        mHdmiControlService.runOnServiceThread(uidReadingRunnable);
+        mHdmiControlServiceSpy.runOnServiceThread(uidReadingRunnable);
 
         Binder.setCallingWorkSourceUid(runnerUid);
 
@@ -666,36 +667,36 @@
     @Test
     public void initCecVersion_limitToMinimumSupportedVersion() {
         mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
 
         mTestLooper.dispatchAll();
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
     }
 
     @Test
     public void initCecVersion_limitToAtLeast1_4() {
         mNativeWrapper.setCecVersion(0x0);
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
 
         mTestLooper.dispatchAll();
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
     }
 
     @Test
     public void initCecVersion_useHighestMatchingVersion() {
         mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlService.getHdmiCecConfig().setIntValue(
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
 
         mTestLooper.dispatchAll();
-        assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+        assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
 
@@ -710,4 +711,140 @@
             this.mVolumeControlEnabled = enabled;
         }
     }
+
+    @Test
+    public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() {
+        // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+        HdmiCecMessage message = HdmiUtils.buildMessage("40:8D:03");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+    }
+
+    @Test
+    public void handleCecCommand_errorSource_returnsHandled() {
+        // Validity ERROR_SOURCE. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+        HdmiCecMessage message = HdmiUtils.buildMessage("F0:8E");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.HANDLED);
+
+    }
+
+    @Test
+    public void handleCecCommand_errorDestination_returnsHandled() {
+        // Validity ERROR_DESTINATION. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+        HdmiCecMessage message = HdmiUtils.buildMessage("0F:8E:00");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.HANDLED);
+    }
+
+    @Test
+    public void handleCecCommand_errorParameterShort_returnsHandled() {
+        // Validity ERROR_PARAMETER_SHORT
+        // Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+        HdmiCecMessage message = HdmiUtils.buildMessage("40:8E");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.HANDLED);
+    }
+
+    @Test
+    public void handleCecCommand_notHandledByLocalDevice_returnsNotHandled() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1,
+                HdmiControlManager.POWER_STATUS_ON);
+
+        doReturn(Constants.NOT_HANDLED).when(mHdmiControlServiceSpy)
+                .dispatchMessageToLocalDevice(message);
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.NOT_HANDLED);
+    }
+
+    @Test
+    public void handleCecCommand_handledByLocalDevice_returnsHandled() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1,
+                HdmiControlManager.POWER_STATUS_ON);
+
+        doReturn(Constants.HANDLED).when(mHdmiControlServiceSpy)
+                .dispatchMessageToLocalDevice(message);
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.HANDLED);
+    }
+
+    @Test
+    public void handleCecCommand_localDeviceReturnsFeatureAbort_returnsFeatureAbort() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1,
+                HdmiControlManager.POWER_STATUS_ON);
+
+        doReturn(Constants.ABORT_REFUSED).when(mHdmiControlServiceSpy)
+                .dispatchMessageToLocalDevice(message);
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+                .isEqualTo(Constants.ABORT_REFUSED);
+    }
+
+    @Test
+    public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+                Constants.ADDR_TV,
+                Constants.ADDR_BROADCAST);
+
+        doReturn(Constants.ABORT_REFUSED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+        doReturn(Constants.ABORT_NOT_IN_CORRECT_MODE)
+                .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+        assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+                .isEqualTo(Constants.HANDLED);
+    }
+
+    @Test
+    public void dispatchMessageToLocalDevice_localDevicesDoNotHandleMessage_returnsUnhandled() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1);
+
+        doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+        doReturn(Constants.NOT_HANDLED)
+                .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+        assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+                .isEqualTo(Constants.NOT_HANDLED);
+    }
+
+    @Test
+    public void dispatchMessageToLocalDevice_localDeviceHandlesMessage_returnsHandled() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1);
+
+        doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+        doReturn(Constants.HANDLED)
+                .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+        assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+                .isEqualTo(Constants.HANDLED);
+    }
+
+    @Test
+    public void dispatchMessageToLocalDevice_localDeviceReturnsFeatureAbort_returnsFeatureAbort() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+                Constants.ADDR_TV,
+                Constants.ADDR_PLAYBACK_1);
+
+        doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+        doReturn(Constants.ABORT_REFUSED)
+                .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+        assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+                .isEqualTo(Constants.ABORT_REFUSED);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index c61635c..d74bff2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -37,13 +37,13 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.test.TestLooper;
+import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -79,12 +79,18 @@
     @Mock
     private IThermalService mIThermalServiceMock;
 
-    @Before
-    public void setUp() throws Exception {
+    /**
+     * Manually called before tests, because some tests require HDMI control to be disabled.
+     * @param hdmiControlEnabled whether to enable the global setting hdmi_control.
+     * @throws Exception
+     */
+    public void setUp(boolean hdmiControlEnabled) throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
+        setHdmiControlEnabled(hdmiControlEnabled);
+
         PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
                 mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
         when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
@@ -146,7 +152,9 @@
     }
 
     @Test
-    public void succeedWithUnknownTvDevice() {
+    public void succeedWithUnknownTvDevice() throws Exception {
+        setUp(true);
+
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
@@ -185,7 +193,9 @@
     }
 
     @Test
-    public void succeedAfterGettingPowerStatusOn_Cec14b() {
+    public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -225,7 +235,9 @@
     }
 
     @Test
-    public void succeedAfterGettingTransientPowerStatus_Cec14b() {
+    public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -275,7 +287,9 @@
     }
 
     @Test
-    public void timeOut_Cec14b() {
+    public void timeOut_Cec14b() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -316,7 +330,9 @@
     }
 
     @Test
-    public void succeedIfPowerStatusOn_Cec20() {
+    public void succeedIfPowerStatusOn_Cec20() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -348,7 +364,9 @@
     }
 
     @Test
-    public void succeedIfPowerStatusUnknown_Cec20() {
+    public void succeedIfPowerStatusUnknown_Cec20() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -390,7 +408,9 @@
     }
 
     @Test
-    public void succeedIfPowerStatusStandby_Cec20() {
+    public void succeedIfPowerStatusStandby_Cec20() throws Exception {
+        setUp(true);
+
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
@@ -431,6 +451,73 @@
         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
     }
 
+    @Test
+    public void succeedWithAddressNotAllocated_Cec14b() throws Exception {
+        setUp(false);
+
+        assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
+
+        HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+                mHdmiControlService);
+        playbackDevice.init();
+        mLocalDevices.add(playbackDevice);
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiControlService.oneTouchPlay(callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.hasResult()).isFalse();
+        assertThat(playbackDevice.isActiveSource()).isFalse();
+
+        setHdmiControlEnabled(true);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV,
+                playbackDevice.mAddress,
+                HdmiControlManager.POWER_STATUS_ON
+        );
+        mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+        assertThat(playbackDevice.isActiveSource()).isTrue();
+    }
+
+    @Test
+    public void succeedWithAddressAllocated_Cec14b() throws Exception {
+        setUp(true);
+
+        HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+                mHdmiControlService);
+        playbackDevice.init();
+        mLocalDevices.add(playbackDevice);
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+
+        TestCallback callback = new TestCallback();
+        mHdmiControlService.oneTouchPlay(callback);
+
+        HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV,
+                playbackDevice.mAddress,
+                HdmiControlManager.POWER_STATUS_ON
+        );
+        mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+        assertThat(playbackDevice.isActiveSource()).isTrue();
+    }
+
     private static class TestActionTimer implements ActionTimer {
         private int mState;
 
@@ -456,9 +543,19 @@
             mCallbackResult.add(result);
         }
 
+        private boolean hasResult() {
+            return mCallbackResult.size() != 0;
+        }
+
         private int getResult() {
             assertThat(mCallbackResult.size()).isEqualTo(1);
             return mCallbackResult.get(0);
         }
     }
+
+    private void setHdmiControlEnabled(boolean enabled) {
+        int value = enabled ? 1 : 0;
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.HDMI_CONTROL_ENABLED, value);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 91342ce..8c08226 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -21,6 +21,7 @@
 import static android.content.pm.UserInfo.FLAG_PROFILE;
 import static android.os.UserHandle.USER_SYSTEM;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -110,6 +111,10 @@
     public interface MockableRebootEscrowInjected {
         int getBootCount();
 
+        long getCurrentTimeMillis();
+
+        boolean forceServerBased();
+
         void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                 int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
     }
@@ -174,6 +179,9 @@
 
         @Override
         public boolean serverBasedResumeOnReboot() {
+            if (mInjected.forceServerBased()) {
+                return true;
+            }
             return mServerBased;
         }
 
@@ -205,9 +213,20 @@
         }
 
         @Override
+        public String getVbmetaDigest(boolean other) {
+            return other ? "" : "fake digest";
+        }
+
+        @Override
+        public long getCurrentTimeMillis() {
+            return mInjected.getCurrentTimeMillis();
+        }
+
+        @Override
         public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                 int escrowDurationInSeconds, int vbmetaDigestStatus,
                 int durationSinceBootComplete) {
+
             mInjected.reportMetric(success, errorCode, serviceType, attemptCount,
                     escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete);
         }
@@ -430,16 +449,21 @@
         // pretend reboot happens here
 
         when(mInjected.getBootCount()).thenReturn(1);
+        when(mInjected.getCurrentTimeMillis()).thenReturn(30000L);
+        mStorage.setLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, 10000L,
+                USER_SYSTEM);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
                 eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */,
-                anyInt(), anyInt(), anyInt());
+                eq(20), eq(0) /* vbmeta status */, anyInt());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
         mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+        assertEquals(mStorage.getLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP,
+                -1, USER_SYSTEM), -1);
     }
 
     @Test
@@ -468,7 +492,7 @@
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
                 eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */,
-                anyInt(), anyInt(), anyInt());
+                anyInt(), eq(0) /* vbmeta status */, anyInt());
 
         when(mServiceConnection.unwrap(any(), anyLong()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -479,6 +503,84 @@
     }
 
     @Test
+    public void loadRebootEscrowDataIfAvailable_ServerBasedRemoteException_Failure()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
+                metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
+                eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
+        mService.loadRebootEscrowDataIfAvailable(null);
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+                metricsErrorCodeCaptor.getValue());
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception {
+        setServerBasedRebootEscrowProvider();
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
+                metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
+                eq(2) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class);
+
+        HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
+        thread.start();
+        mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper()));
+        // Sleep 5s for the retry to complete
+        Thread.sleep(5 * 1000);
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_RETRY_COUNT_EXHAUSTED),
+                metricsErrorCodeCaptor.getValue());
+    }
+
+    @Test
     public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception {
         setServerBasedRebootEscrowProvider();
 
@@ -607,9 +709,14 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
+        // Trigger a vbmeta digest mismatch
+        mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+                "non sense value", USER_SYSTEM);
         mService.loadRebootEscrowDataIfAvailable(null);
         verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */,
-                eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
+                eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt());
+        assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+                "", USER_SYSTEM), "");
     }
 
     @Test
@@ -636,12 +743,17 @@
 
         when(mInjected.getBootCount()).thenReturn(1);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        // Return a null escrow key
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
-                anyInt() /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */,
-                anyInt(), anyInt(), anyInt());
-        when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
+                metricsErrorCodeCaptor.capture(), eq(1) /* HAL based */,
+                eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
+
+        when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> null);
         mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+                metricsErrorCodeCaptor.getValue());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index fb01ff6..d405113 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1817,7 +1817,7 @@
         // yet reached.
         final NetworkPolicyManagerInternal npmi = LocalServices
                 .getService(NetworkPolicyManagerInternal.class);
-        npmi.onStatsProviderLimitReached("TEST");
+        npmi.onStatsProviderWarningOrLimitReached("TEST");
 
         // Verifies that the limit reached leads to a force update and new limit should be set.
         postMsgAndWaitForCompletion();
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 1ab70e5..212a9c6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -641,7 +641,7 @@
         public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
                 List<String> schemasNotPlatformSurfaceable,
                 Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride,
-                int userId, IAppSearchResultCallback callback) throws RemoteException {
+                int userId, int version, IAppSearchResultCallback callback) throws RemoteException {
             for (Map.Entry<String, List<Bundle>> entry :
                     schemasPackageAccessibleBundles.entrySet()) {
                 final String key = entry.getKey();
@@ -666,6 +666,12 @@
         }
 
         @Override
+        public void getNamespaces(String packageName, String databaseName, int userId,
+                IAppSearchResultCallback callback) throws RemoteException {
+            ignore(callback);
+        }
+
+        @Override
         public void putDocuments(String packageName, String databaseName,
                 List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
                 throws RemoteException {
@@ -764,7 +770,8 @@
 
         @Override
         public void reportUsage(String packageName, String databaseName, String namespace,
-                String uri, long usageTimeMillis, int userId, IAppSearchResultCallback callback)
+                String uri, long usageTimeMillis, boolean systemUsage, int userId,
+                IAppSearchResultCallback callback)
                 throws RemoteException {
             ignore(callback);
         }
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 324e592..7903a90 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -72,6 +73,7 @@
     private LockSettingsInternal mLockSettingsInternal;
     private IBootControl mIBootControl;
     private RecoverySystemServiceTestable.IMetricsReporter mMetricsReporter;
+    private RecoverySystemService.PreferencesManager mSharedPreferences;
 
     private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package";
     private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package";
@@ -97,10 +99,11 @@
         when(mIBootControl.getActiveBootSlot()).thenReturn(1);
 
         mMetricsReporter = mock(RecoverySystemServiceTestable.IMetricsReporter.class);
+        mSharedPreferences = mock(RecoverySystemService.PreferencesManager.class);
 
         mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
                 powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal,
-                mIBootControl, mMetricsReporter);
+                mIBootControl, mMetricsReporter, mSharedPreferences);
     }
 
     @Test
@@ -237,6 +240,8 @@
                 is(true));
         verify(mMetricsReporter).reportRebootEscrowPreparationMetrics(
                 eq(1000), eq(0) /* need preparation */, eq(1) /* client count */);
+        verify(mSharedPreferences).putLong(eq(FAKE_OTA_PACKAGE_NAME
+                + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), eq(100_000L));
     }
 
 
@@ -245,10 +250,19 @@
         IntentSender intentSender = mock(IntentSender.class);
         assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
                 is(true));
+
+        when(mSharedPreferences.getLong(eq(FAKE_OTA_PACKAGE_NAME
+                + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), anyLong()))
+                .thenReturn(200_000L).thenReturn(5000L);
+        mRecoverySystemService.onPreparedForReboot(true);
+        verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics(
+                eq(1000), eq(1) /* client count */,
+                eq(-1) /* invalid duration */);
+
         mRecoverySystemService.onPreparedForReboot(true);
         verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
         verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics(
-                eq(1000), eq(1) /* client count */, anyInt() /* duration */);
+                eq(1000), eq(1) /* client count */, eq(95) /* duration */);
     }
 
     @Test
@@ -352,12 +366,19 @@
     public void rebootWithLskf_Success() throws Exception {
         assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
         mRecoverySystemService.onPreparedForReboot(true);
+
+        when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME
+                + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+        when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+                anyInt())).thenReturn(3);
+        when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+                anyLong())).thenReturn(40_000L);
         assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
                 is(true));
         verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
         verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000),
-                eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
-                anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+                eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+                anyBoolean(), eq(60) /* duration */, eq(3) /* lskf capture count */);
     }
 
 
@@ -400,13 +421,19 @@
         assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
         mRecoverySystemService.onPreparedForReboot(true);
 
-        // Client B's clear won't affect client A's preparation.
+        when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME
+                + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+        when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+                anyInt())).thenReturn(1);
+        when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+                anyLong())).thenReturn(60_000L);
+
         assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
                 is(true));
         verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
         verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000),
-                eq(2) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
-                anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+                eq(2) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+                anyBoolean(), eq(40), eq(1) /* lskf capture count */);
     }
 
     @Test
@@ -415,22 +442,30 @@
         mRecoverySystemService.onPreparedForReboot(true);
         assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
 
+        when(mSharedPreferences.getInt(eq(FAKE_OTHER_PACKAGE_NAME
+                + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+        when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+                anyInt())).thenReturn(1);
+        when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+                anyLong())).thenReturn(60_000L);
+
         assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
         assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
                 is(false));
         verifyNoMoreInteractions(mIPowerManager);
         verify(mMetricsReporter).reportRebootEscrowRebootMetrics(not(eq(0)), eq(1000),
-                eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
-                anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+                eq(1) /* client count */, anyInt() /* request count */, eq(true) /* slot switch */,
+                anyBoolean(), eq(40), eq(1)/* lskf capture count */);
 
         assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
         assertThat(
                 mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true),
                 is(true));
         verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
-        verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(2000),
-                eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
-                anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+
+        verify(mMetricsReporter).reportRebootEscrowRebootMetrics((eq(0)), eq(2000),
+                eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+                anyBoolean(), eq(40), eq(1) /* lskf capture count */);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
index a894178..27e953f 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -33,11 +33,13 @@
         private final LockSettingsInternal mLockSettingsInternal;
         private final IBootControl mIBootControl;
         private final IMetricsReporter mIMetricsReporter;
+        private final RecoverySystemService.PreferencesManager mSharedPreferences;
 
         MockInjector(Context context, FakeSystemProperties systemProperties,
                 PowerManager powerManager, FileWriter uncryptPackageFileWriter,
                 UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
-                IBootControl bootControl, IMetricsReporter metricsReporter) {
+                IBootControl bootControl, IMetricsReporter metricsReporter,
+                RecoverySystemService.PreferencesManager preferences) {
             super(context);
             mSystemProperties = systemProperties;
             mPowerManager = powerManager;
@@ -46,6 +48,7 @@
             mLockSettingsInternal = lockSettingsInternal;
             mIBootControl = bootControl;
             mIMetricsReporter = metricsReporter;
+            mSharedPreferences = preferences;
         }
 
         @Override
@@ -114,12 +117,14 @@
                     requestedClientCount);
         }
 
+        @Override
         public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount,
                 int requestedToLskfCapturedDurationInSeconds) {
             mIMetricsReporter.reportRebootEscrowLskfCapturedMetrics(uid, requestedClientCount,
                     requestedToLskfCapturedDurationInSeconds);
         }
 
+        @Override
         public void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount,
                 int requestCount, boolean slotSwitch, boolean serverBased,
                 int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) {
@@ -127,14 +132,25 @@
                     requestCount, slotSwitch, serverBased, lskfCapturedToRebootDurationInSeconds,
                     lskfCapturedCounts);
         }
+
+        @Override
+        public long getCurrentTimeMillis() {
+            return 100_000;
+        }
+
+        @Override
+        public RecoverySystemService.PreferencesManager getMetricsPrefs() {
+            return mSharedPreferences;
+        }
     }
 
     RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
             PowerManager powerManager, FileWriter uncryptPackageFileWriter,
             UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
-            IBootControl bootControl, IMetricsReporter metricsReporter) {
+            IBootControl bootControl, IMetricsReporter metricsReporter,
+            RecoverySystemService.PreferencesManager preferences) {
         super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
-                uncryptSocket, lockSettingsInternal, bootControl, metricsReporter));
+                uncryptSocket, lockSettingsInternal, bootControl, metricsReporter, preferences));
     }
 
     public static class FakeSystemProperties {
@@ -176,5 +192,4 @@
                 int requestCount, boolean slotSwitch, boolean serverBased,
                 int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts);
     }
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 1068270..742f503 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -217,20 +217,20 @@
             fail();
         } finally {
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SET_TIME), anyString());
+                    eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
         }
     }
 
     @Test
     public void testSuggestExternalTime() throws Exception {
-        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
         mTimeDetectorService.suggestExternalTime(externalTimeSuggestion);
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SET_TIME), anyString());
+                eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestExternalTimeCalled(externalTimeSuggestion);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 86b1620..8991e9f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -940,7 +940,22 @@
                 .setLong("elapsed_threshold_restricted", -1);
         mInjector.mPropertiesChangedListener
                 .onPropertiesChanged(mInjector.getDeviceConfigProperties());
-        testTimeout();
+
+        reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        mInjector.mElapsedRealtime = HOUR_MS;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_FREQUENT);
+
+        mInjector.mElapsedRealtime = 2 * HOUR_MS;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_RARE);
+
+        mInjector.mElapsedRealtime = 4 * HOUR_MS;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
new file mode 100644
index 0000000..dcff479
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link DeviceVibrationEffectAdapter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:DeviceVibrationEffectAdapterTest
+ */
+@Presubmit
+public class DeviceVibrationEffectAdapterTest {
+    private static final float TEST_MIN_FREQUENCY = 50;
+    private static final float TEST_RESONANT_FREQUENCY = 150;
+    private static final float TEST_FREQUENCY_RESOLUTION = 25;
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+    private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
+            new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
+    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
+            new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY,
+                    TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION,
+                    /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP);
+
+    private DeviceVibrationEffectAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new DeviceVibrationEffectAdapter();
+    }
+
+    @Test
+    public void testPrebakedAndPrimitiveSegments_returnsOriginalSegment() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+                /* repeatIndex= */ -1);
+
+        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
+        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+    }
+
+    @Test
+    public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
+                /* repeatIndex= */ 2);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN,
+                        /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
+                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
+                        /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
+                        /* duration= */ 20)),
+                /* repeatIndex= */ 2);
+
+        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
+    }
+
+    @Test
+    public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
+                /* repeatIndex= */ 2);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
+                        /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
+                        /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
+                /* repeatIndex= */ 2);
+
+        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+    }
+
+    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping) {
+        return new VibratorInfo(/* id= */ 0, /* capabilities= */ 0, null, null,
+                /* qFactor= */ Float.NaN, frequencyMapping);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index a959a5e..1e3c344 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -75,8 +75,8 @@
 
         @Override
         public void on(long milliseconds, long vibrationId) {
-            mEffectSegments.add(
-                    new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) milliseconds));
+            mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
+                    /* frequency= */ 0, (int) milliseconds));
             applyLatency();
             scheduleListener(milliseconds, vibrationId);
         }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 739a1a3..37e0ec2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -921,7 +921,7 @@
     }
 
     private VibrationEffectSegment expectedOneShot(long millis) {
-        return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) millis);
+        return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequency= */ 0, (int) millis);
     }
 
     private VibrationEffectSegment expectedPrebaked(int effectId) {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index 0ba3a21b..70ea219 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -41,6 +41,7 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -227,6 +228,18 @@
     }
 
     @Test
+    public void on_withComposedPwle_ignoresEffect() {
+        VibratorController controller = createController();
+
+        RampSegment[] primitives = new RampSegment[]{
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 10)
+        };
+        assertEquals(0L, controller.on(primitives, 12));
+        assertFalse(controller.isVibrating());
+    }
+
+    @Test
     public void off_turnsOffVibrator() {
         VibratorController controller = createController();
         controller.on(100, 1);
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index c6e35cf..e932905 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -93,3 +93,24 @@
         spyThrowOnUnmocked<T>(null, block)
 
 inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+/**
+ * Wrapper around [Mockito.any] for generic types.
+ */
+inline fun <reified T> any() = any(T::class.java)
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS
new file mode 100644
index 0000000..d825dfd
--- /dev/null
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pm/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index aa9feeaa..55ebe11 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -900,10 +900,10 @@
 
     @Test
     public void testDefaultAssistant_overrideDefault() {
-        final int userId = 0;
+        final int userId = mContext.getUserId();
         final String testComponent = "package/class";
         final List<UserInfo> userInfos = new ArrayList<>();
-        userInfos.add(new UserInfo(0, "", 0));
+        userInfos.add(new UserInfo(userId, "", 0));
         final ArraySet<ComponentName> validAssistants = new ArraySet<>();
         validAssistants.add(ComponentName.unflattenFromString(testComponent));
         when(mActivityManager.isLowRamDevice()).thenReturn(false);
@@ -2346,7 +2346,7 @@
                 .thenReturn(mTestNotificationChannel);
 
         reset(mListeners);
-        mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+        mBinderService.updateNotificationChannelForPackage(PKG, mUid, mTestNotificationChannel);
         verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
                 eq(Process.myUserHandle()), eq(mTestNotificationChannel),
                 eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
@@ -2882,7 +2882,7 @@
 
     @Test
     public void testSetListenerAccessForUser() throws Exception {
-        UserHandle user = UserHandle.of(10);
+        UserHandle user = UserHandle.of(mContext.getUserId() + 10);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         mBinderService.setNotificationListenerAccessGrantedForUser(
                 c, user.getIdentifier(), true, true);
@@ -2899,20 +2899,20 @@
 
     @Test
     public void testSetAssistantAccessForUser() throws Exception {
-        UserHandle user = UserHandle.of(10);
-        List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 10;
+        ui.id = mContext.getUserId() + 10;
+        UserHandle user = UserHandle.of(ui.id);
+        List<UserInfo> uis = new ArrayList<>();
         uis.add(ui);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
-        when(mUm.getEnabledProfiles(10)).thenReturn(uis);
+        when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
 
         mBinderService.setNotificationAssistantAccessGrantedForUser(c, user.getIdentifier(), true);
 
         verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), user.getIdentifier(), true, true, true);
-        verify(mAssistants).setUserSet(10, true);
+        verify(mAssistants).setUserSet(ui.id, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), user.getIdentifier(), false, true);
         verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -2921,7 +2921,7 @@
 
     @Test
     public void testGetAssistantAllowedForUser() throws Exception {
-        UserHandle user = UserHandle.of(10);
+        UserHandle user = UserHandle.of(mContext.getUserId() + 10);
         try {
             mBinderService.getAllowedNotificationAssistantForUser(user.getIdentifier());
         } catch (IllegalStateException e) {
@@ -2941,12 +2941,12 @@
                 throw e;
             }
         }
-        verify(mAssistants, times(1)).getAllowedComponents(0);
+        verify(mAssistants, times(1)).getAllowedComponents(mContext.getUserId());
     }
 
     @Test
     public void testSetDndAccessForUser() throws Exception {
-        UserHandle user = UserHandle.of(10);
+        UserHandle user = UserHandle.of(mContext.getUserId() + 10);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         mBinderService.setNotificationPolicyAccessGrantedForUser(
                 c.getPackageName(), user.getIdentifier(), true);
@@ -2966,9 +2966,9 @@
         mBinderService.setNotificationListenerAccessGranted(c, true, true);
 
         verify(mListeners, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true, true);
+                c.flattenToString(), mContext.getUserId(), true, true, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true, true);
+                c.flattenToString(), mContext.getUserId(), false, true, true);
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
     }
@@ -2977,7 +2977,7 @@
     public void testSetAssistantAccess() throws Exception {
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -2985,9 +2985,9 @@
         mBinderService.setNotificationAssistantAccessGranted(c, true);
 
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true, true);
+                c.flattenToString(), ui.id, true, true, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true);
+                c.flattenToString(), ui.id, false, true);
         verify(mListeners, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
@@ -2996,10 +2996,10 @@
     public void testSetAssistantAccess_multiProfile() throws Exception {
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         UserInfo ui10 = new UserInfo();
-        ui10.id = 10;
+        ui10.id = mContext.getUserId() + 10;
         uis.add(ui10);
         when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -3007,13 +3007,14 @@
         mBinderService.setNotificationAssistantAccessGranted(c, true);
 
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true, true);
+                c.flattenToString(), ui.id, true, true, true);
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 10, true, true, true);
+                c.flattenToString(), ui10.id, true, true, true);
+
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true);
+                c.flattenToString(), ui.id, false, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 10, false, true);
+                c.flattenToString(), ui10.id, false, true);
         verify(mListeners, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
@@ -3026,16 +3027,16 @@
         when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList);
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
 
         mBinderService.setNotificationAssistantAccessGranted(null, true);
 
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, false, true);
+                c.flattenToString(), ui.id, true, false, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false,  false);
+                c.flattenToString(), ui.id, false,  false);
         verify(mListeners, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
@@ -3044,21 +3045,21 @@
     public void testSetAssistantAccessForUser_nullWithAllowedAssistant() throws Exception {
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 10;
+        ui.id = mContext.getUserId() + 10;
         uis.add(ui);
         UserHandle user = ui.getUserHandle();
         ArrayList<ComponentName> componentList = new ArrayList<>();
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         componentList.add(c);
         when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList);
-        when(mUm.getEnabledProfiles(10)).thenReturn(uis);
+        when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
 
         mBinderService.setNotificationAssistantAccessGrantedForUser(
                 null, user.getIdentifier(), true);
 
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), user.getIdentifier(), true, false, true);
-        verify(mAssistants).setUserSet(10, true);
+        verify(mAssistants).setUserSet(ui.id, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), user.getIdentifier(), false,  false);
         verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -3070,10 +3071,10 @@
             throws Exception {
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         UserInfo ui10 = new UserInfo();
-        ui10.id = 10;
+        ui10.id = mContext.getUserId() + 10;
         uis.add(ui10);
         UserHandle user = ui.getUserHandle();
         ArrayList<ComponentName> componentList = new ArrayList<>();
@@ -3089,8 +3090,8 @@
                 c.flattenToString(), user.getIdentifier(), true, false, true);
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), ui10.id, true, false, true);
-        verify(mAssistants).setUserSet(0, true);
-        verify(mAssistants).setUserSet(10, true);
+        verify(mAssistants).setUserSet(ui.id, true);
+        verify(mAssistants).setUserSet(ui10.id, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), user.getIdentifier(), false,  false);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
@@ -3106,7 +3107,7 @@
         mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
 
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.getPackageName(), 0, true, true);
+                c.getPackageName(), mContext.getUserId(), true, true);
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
         verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -3133,7 +3134,7 @@
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
 
@@ -3168,9 +3169,9 @@
         mBinderService.setNotificationListenerAccessGranted(c, true, true);
 
         verify(mListeners, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true, true);
+                c.flattenToString(), mContext.getUserId(), true, true, true);
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true, true);
+                c.flattenToString(), mContext.getUserId(), false, true, true);
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
     }
@@ -3182,7 +3183,7 @@
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         List<UserInfo> uis = new ArrayList<>();
         UserInfo ui = new UserInfo();
-        ui.id = 0;
+        ui.id = mContext.getUserId();
         uis.add(ui);
         when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
 
@@ -3191,9 +3192,9 @@
         verify(mListeners, never()).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true);
+                c.flattenToString(), ui.id, false, true);
         verify(mAssistants, times(1)).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true, true);
+                c.flattenToString(), ui.id, true, true, true);
     }
 
     @Test
@@ -3207,7 +3208,7 @@
         verify(mListeners, never()).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
-                c.getPackageName(), 0, true, true);
+                c.getPackageName(), mContext.getUserId(), true, true);
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index adf8fa4..24b4f65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -330,21 +330,6 @@
         assertEquals(startingWin, imeTarget);
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
-    @Test
-    public void testComputeImeTarget_placeImeToTheTargetRoot() {
-        ActivityRecord activity = createActivityRecord(mDisplayContent);
-
-        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
-                "startingWin");
-        startingWin.setHasSurface(true);
-        assertTrue(startingWin.canBeImeTarget());
-        DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
-
-        WindowState imeTarget = mDisplayContent.computeImeTarget(true /* updateImeTarget */);
-        verify(imeTarget.getRootDisplayArea()).placeImeContainer(imeContainer);
-    }
-
     @Test
     public void testUpdateImeParent_forceUpdateRelativeLayer() {
         final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e9c356d..e9907c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -311,6 +311,41 @@
     }
 
     @Test
+    public void testPlaceImeContainer_hidesImeWhenParentChanges() {
+        setupImeWindow();
+        final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
+        final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
+        final WindowState firstActivityWin =
+                createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
+                        "firstActivityWin");
+        spyOn(firstActivityWin);
+        final WindowState secondActivityWin =
+                createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
+                        "secondActivityWin");
+        spyOn(secondActivityWin);
+
+        // firstActivityWin should be the target
+        doReturn(true).when(firstActivityWin).canBeImeTarget();
+        doReturn(false).when(secondActivityWin).canBeImeTarget();
+
+        WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
+        assertThat(imeTarget).isEqualTo(firstActivityWin);
+        verify(mFirstRoot).placeImeContainer(imeContainer);
+
+        // secondActivityWin should be the target
+        doReturn(false).when(firstActivityWin).canBeImeTarget();
+        doReturn(true).when(secondActivityWin).canBeImeTarget();
+
+        spyOn(mDisplay.mInputMethodWindow);
+        imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
+
+        assertThat(imeTarget).isEqualTo(secondActivityWin);
+        verify(mSecondRoot).placeImeContainer(imeContainer);
+        // verify hide() was called on InputMethodWindow.
+        verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */);
+    }
+
+    @Test
     public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 925b6f9..6ffdb09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -219,7 +219,7 @@
     }
 
     @Test
-    public void testAddTasksNoMultiple_expectNoTrim() {
+    public void testAddDocumentTasksNoMultiple_expectNoTrim() {
         // Add same non-multiple-task document tasks will remove the task (to re-add it) but not
         // trim it
         Task documentTask1 = createDocumentTask(".DocumentTask1");
@@ -262,7 +262,7 @@
     }
 
     @Test
-    public void testAddTasksMultipleDocumentTasks_expectNoTrim() {
+    public void testAddMultipleDocumentTasks_expectNoTrim() {
         // Add same multiple-task document tasks does not trim the first tasks
         Task documentTask1 = createDocumentTask(".DocumentTask1",
                 FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -278,9 +278,33 @@
     }
 
     @Test
-    public void testAddTasksMultipleTasks_expectRemovedNoTrim() {
-        // Add multiple same-affinity non-document tasks, ensure that it removes the other task,
-        // but that it does not trim it
+    public void testAddTasks_expectRemovedNoTrim() {
+        // Add multiple same-affinity non-document tasks, ensure that it removes, but does not trim
+        // the other task
+        Task task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        Task task2 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        mRecentTasks.add(task1);
+        assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+        assertThat(mCallbacksRecorder.mAdded).contains(task1);
+        assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+        assertThat(mCallbacksRecorder.mRemoved).isEmpty();
+        mCallbacksRecorder.clear();
+        mRecentTasks.add(task2);
+        assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+        assertThat(mCallbacksRecorder.mAdded).contains(task2);
+        assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+        assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+        assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+    }
+
+    @Test
+    public void testAddMultipleTasks_expectNotRemoved() {
+        // Add multiple same-affinity non-document tasks with MULTIPLE_TASK, ensure that it does not
+        // remove the other task
         Task task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .build();
@@ -297,8 +321,7 @@
         assertThat(mCallbacksRecorder.mAdded).hasSize(1);
         assertThat(mCallbacksRecorder.mAdded).contains(task2);
         assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
-        assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
-        assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+        assertThat(mCallbacksRecorder.mRemoved).isEmpty();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 16e0d90..ed5f1d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -38,6 +38,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.window.WindowContext;
 
 import androidx.test.filters.SmallTest;
 
@@ -208,7 +209,7 @@
 
     /**
      * Test that {@link android.view.SurfaceControl} should not be created for the
-     * {@link WindowToken} which was created for {@link android.app.WindowContext} initially, the
+     * {@link WindowToken} which was created for {@link WindowContext} initially, the
      * surface should be create after addWindow for this token.
      */
     @Test
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 6203ae9..f132b49 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -17,13 +17,19 @@
 package com.android.server.translation;
 
 import static android.Manifest.permission.MANAGE_UI_TRANSLATION;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
+
+import static com.android.internal.util.SyncResultReceiver.bundleFor;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -31,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.translation.ITranslationManager;
@@ -231,6 +238,46 @@
             }
         }
 
+        @Override
+        public void getServiceSettingsActivity(IResultReceiver result, int userId) {
+            final TranslationManagerServiceImpl service;
+            synchronized (mLock) {
+                service = getServiceForUserLocked(userId);
+            }
+            if (service != null) {
+                final ComponentName componentName = service.getServiceSettingsActivityLocked();
+                if (componentName == null) {
+                    try {
+                        result.send(STATUS_SYNC_CALL_SUCCESS, null);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+                    }
+                }
+                final Intent intent = new Intent();
+                intent.setComponent(componentName);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    final PendingIntent pendingIntent =
+                            PendingIntent.getActivityAsUser(getContext(), 0, intent, FLAG_IMMUTABLE,
+                                    null, new UserHandle(userId));
+                    try {
+
+                        result.send(STATUS_SYNC_CALL_SUCCESS, bundleFor(pendingIntent));
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } else {
+                try {
+                    result.send(STATUS_SYNC_CALL_FAIL, null);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+                }
+            }
+        }
+
         /**
          * Dump the service state into the given stream. You run "adb shell dumpsys translation".
         */
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index ee5ec47..2cd41ba 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -62,6 +62,9 @@
     @Nullable
     private ServiceInfo mRemoteTranslationServiceInfo;
 
+    @GuardedBy("mLock")
+    private TranslationServiceInfo mTranslationServiceInfo;
+
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
 
     protected TranslationManagerServiceImpl(
@@ -76,10 +79,10 @@
     @Override // from PerUserSystemService
     protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
             throws PackageManager.NameNotFoundException {
-        final TranslationServiceInfo info = new TranslationServiceInfo(getContext(),
+        mTranslationServiceInfo = new TranslationServiceInfo(getContext(),
                 serviceComponent, isTemporaryServiceSetLocked(), mUserId);
-        mRemoteTranslationServiceInfo = info.getServiceInfo();
-        return info.getServiceInfo();
+        mRemoteTranslationServiceInfo = mTranslationServiceInfo.getServiceInfo();
+        return mTranslationServiceInfo.getServiceInfo();
     }
 
     @GuardedBy("mLock")
@@ -227,4 +230,16 @@
     }
 
     private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+    public ComponentName getServiceSettingsActivityLocked() {
+        if (mTranslationServiceInfo == null) {
+            return null;
+        }
+        final String activityName = mTranslationServiceInfo.getSettingsActivity();
+        if (activityName == null) {
+            return null;
+        }
+        final String packageName = mTranslationServiceInfo.getServiceInfo().packageName;
+        return new ComponentName(packageName, activityName);
+    }
 }
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 748e5df..22a6445 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -7,6 +7,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsAssistTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index e089995a..6541774 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -26,12 +26,12 @@
 import android.media.AudioAttributes;
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
-import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
-import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordRejectedResult;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.service.voice.IHotwordDetectionService;
 import android.util.Pair;
@@ -77,7 +77,7 @@
     boolean mBound;
 
     HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName,
-            int userId, boolean bindInstantServiceAllowed, @Nullable Bundle options,
+            int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) {
         mLock = lock;
         mContext = context;
@@ -129,7 +129,7 @@
         }
     }
 
-    void setConfigLocked(Bundle options, SharedMemory sharedMemory) {
+    void setConfigLocked(PersistableBundle options, SharedMemory sharedMemory) {
         mRemoteHotwordDetectionService.run(
                 service -> service.setConfig(options, sharedMemory));
     }
@@ -206,13 +206,12 @@
             }
 
             @Override
-            public void onRejected() throws RemoteException {
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
                 if (DEBUG) {
                     Slog.d(TAG, "onRejected");
                 }
                 cancelingFuture.cancel(true);
-                externalCallback.onRejected(
-                        AlwaysOnHotwordDetector.HOTWORD_DETECTION_FALSE_ALERT);
+                externalCallback.onRejected(result);
             }
         };
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 80d4f8f..29354eb 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -983,7 +984,7 @@
         }
 
         @Override
-        public void setHotwordDetectionServiceConfig(@Nullable Bundle options,
+        public void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
                 @Nullable SharedMemory sharedMemory) {
             enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
             synchronized (this) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index df3ca99..0d4c302 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -43,6 +43,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -53,7 +54,6 @@
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionServiceInfo;
 import android.system.OsConstants;
-import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.view.IWindowManager;
@@ -63,6 +63,7 @@
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.server.LocalServices;
+import com.android.server.wm.ActivityAssistInfo;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
 
@@ -186,24 +187,23 @@
                     mSessionComponentName, mUser, mContext, this,
                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
         }
-        List<Pair<IBinder, Integer>> allVisibleActivities =
+        List<ActivityAssistInfo> allVisibleActivities =
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .getTopVisibleActivities();
 
-        List<Pair<IBinder, Integer>> visibleActivities = null;
+        List<ActivityAssistInfo> visibleActivities = null;
         if (activityToken != null) {
             visibleActivities = new ArrayList();
             int activitiesCount = allVisibleActivities.size();
             for (int i = 0; i < activitiesCount; i++) {
-                if (allVisibleActivities.get(i).first == activityToken) {
-                    visibleActivities.add(
-                            new Pair<>(activityToken, allVisibleActivities.get(i).second));
+                ActivityAssistInfo info = allVisibleActivities.get(i);
+                if (info.getActivityToken() == activityToken) {
+                    visibleActivities.add(info);
                     break;
                 }
             }
         } else {
-            visibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class)
-                    .getTopVisibleActivities();
+            visibleActivities = allVisibleActivities;
         }
         return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
                 visibleActivities);
@@ -401,7 +401,7 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
-    public void setHotwordDetectionServiceConfigLocked(@Nullable Bundle options,
+    public void setHotwordDetectionServiceConfigLocked(@Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory) {
         if (DEBUG) {
             Slog.d(TAG, "setHotwordDetectionServiceConfigLocked");
@@ -415,7 +415,6 @@
             throw new IllegalStateException("Hotword detection service not in isolated process");
         }
         // TODO : Need to check related permissions for hotword detection service
-        // TODO : Sanitize for bundle
 
         if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
             Slog.w(TAG, "Can't set sharedMemory to be read-only");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 428d342..cc021a9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -56,7 +56,6 @@
 import android.service.voice.IVoiceInteractionSessionService;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionSession;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.IWindowManager;
 
@@ -68,6 +67,7 @@
 import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.ActivityAssistInfo;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -102,6 +102,7 @@
     IVoiceInteractionSession mSession;
     IVoiceInteractor mInteractor;
     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
+    private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
     AssistDataRequester mAssistDataRequester;
 
     IVoiceInteractionSessionShowCallback mShowCallback =
@@ -192,7 +193,7 @@
 
     public boolean showLocked(Bundle args, int flags, int disabledContext,
             IVoiceInteractionSessionShowCallback showCallback,
-            List<Pair<IBinder, Integer>> topActivities) {
+            List<ActivityAssistInfo> topActivities) {
         if (mBound) {
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -216,7 +217,7 @@
                 int topActivitiesCount = topActivities.size();
                 final ArrayList<IBinder> topActivitiesToken = new ArrayList<>(topActivitiesCount);
                 for (int i = 0; i < topActivitiesCount; i++) {
-                    topActivitiesToken.add(topActivities.get(i).first);
+                    topActivitiesToken.add(topActivities.get(i).getActivityToken());
                 }
                 mAssistDataRequester.requestAssistData(topActivitiesToken,
                         fetchData,
@@ -243,8 +244,16 @@
                 } else {
                     doHandleAssistWithoutData(topActivities);
                 }
-            } else if (showCallback != null) {
-                mPendingShowCallbacks.add(showCallback);
+            } else {
+                if (showCallback != null) {
+                    mPendingShowCallbacks.add(showCallback);
+                }
+                if (!assistDataRequestNeeded) {
+                    // If no data are required we are not passing trough mAssistDataRequester. As
+                    // a consequence, when a new session is delivered it is needed to process those
+                    // requests manually.
+                    mPendingHandleAssistWithoutData = topActivities;
+                }
             }
             mCallback.onSessionShown(this);
             return true;
@@ -258,17 +267,17 @@
         return false;
     }
 
-    private void doHandleAssistWithoutData(List<Pair<IBinder, Integer>> topActivities) {
+    private void doHandleAssistWithoutData(List<ActivityAssistInfo> topActivities) {
         final int activityCount = topActivities.size();
         for (int i = 0; i < activityCount; i++) {
-            final Pair<IBinder, Integer> topActivity = topActivities.get(i);
-            final IBinder activityId = topActivity.first;
-            final int taskId = topActivity.second;
+            final ActivityAssistInfo topActivity = topActivities.get(i);
+            final IBinder assistToken = topActivity.getAssistToken();
+            final int taskId = topActivity.getTaskId();
             final int activityIndex = i;
             try {
                 mSession.handleAssist(
                         taskId,
-                        activityId,
+                        assistToken,
                         /* assistData = */ null,
                         /* assistStructure = */ null,
                         /* assistContent = */ null,
@@ -468,6 +477,10 @@
             } catch (RemoteException e) {
             }
             mAssistDataRequester.processPendingAssistData();
+            if (!mPendingHandleAssistWithoutData.isEmpty()) {
+                doHandleAssistWithoutData(mPendingHandleAssistWithoutData);
+                mPendingHandleAssistWithoutData.clear();
+            }
         }
         return true;
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 04a0aba..3db31ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4254,6 +4254,14 @@
         public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT =
                 KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int";
 
+        /**
+         * Specifies the RCS feature tag allowed for the carrier.
+         *
+         * <p>The values refer to RCC.07 2.4.4.
+         */
+        public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY =
+                KEY_PREFIX + "rcs_feature_tag_allowed_string_array";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -4267,6 +4275,27 @@
             defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true);
             defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
+            defaults.putStringArray(KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, new String[]{
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"",
+                    "+g.gsma.rcs.cpm.pager-large",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"",
+                    "+g.gsma.callcomposer",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedmap\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedsketch\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopush\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geosms\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"",
+                    "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\"",
+                    "+g.gsma.rcs.botversion=\"#=1,#=2\"",
+                    "+g.gsma.rcs.cpimext"});
+
             return defaults;
         }
     }
@@ -4849,6 +4878,30 @@
     public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL =
             "carrier_provisions_wifi_merged_networks_bool";
 
+    /**
+     * Determines whether or not to use (IP) data connectivity as a supplemental condition to
+     * control the visibility of the no-calling indicator for this carrier in the System UI. Setting
+     * the configuration to true may make sense to a carrier which provides OTT calling.
+     *
+     * Config = true: do not show no-calling indication if (IP) data connectivity is available
+     *                or telephony has voice registration.
+     * Config = false: do not show no-calling indication if telephony has voice registration.
+     */
+    public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL =
+            "hide_no_calling_indicator_on_data_network_bool";
+
+    /**
+     * Determine whether or not to display a call strength indicator for this carrier in the System
+     * UI. Disabling the indication may be reasonable if the carrier's calling is not integrated
+     * into the Android telephony stack (e.g. it is OTT).
+     *
+     * true: Use telephony APIs to detect the current networking medium of calling and display a
+     *       UI indication based on the current strength (e.g. signal level) of that medium.
+     * false: Do not display the call strength indicator.
+     */
+    public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL =
+            "display_call_strength_indicator_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5422,6 +5475,8 @@
         sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
                 new String[]{"ia", "default", "ims", "mms", "dun", "emergency"});
         sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
+        sDefaults.putBoolean(KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL, false);
+        sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true);
         sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
     }
 
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 30480d1..a3aaf61 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -26,6 +26,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -47,26 +48,18 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "DEVICE_NR_CAPABILITY_" }, value = {
-            DEVICE_NR_CAPABILITY_NONE,
             DEVICE_NR_CAPABILITY_NSA,
             DEVICE_NR_CAPABILITY_SA,
     })
     public @interface DeviceNrCapability {}
 
     /**
-     * Indicates DEVICE_NR_CAPABILITY_NONE determine that the device does not enable 5G NR.
-     * @hide
-     */
-    @SystemApi
-    public static final int DEVICE_NR_CAPABILITY_NONE = 0;
-
-    /**
      * Indicates DEVICE_NR_CAPABILITY_NSA determine that the device enable the non-standalone
      * (NSA) mode of 5G NR.
      * @hide
      */
     @SystemApi
-    public static final int DEVICE_NR_CAPABILITY_NSA = 1 << 0;
+    public static final int DEVICE_NR_CAPABILITY_NSA = 1;
 
     /**
      * Indicates DEVICE_NR_CAPABILITY_SA determine that the device enable the standalone (SA)
@@ -74,7 +67,7 @@
      * @hide
      */
     @SystemApi
-    public static final int DEVICE_NR_CAPABILITY_SA = 1 << 1;
+    public static final int DEVICE_NR_CAPABILITY_SA = 2;
 
     static {
         ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
@@ -83,31 +76,34 @@
         List<ModemInfo> logicalModemList = new ArrayList<>();
         logicalModemList.add(modemInfo1);
         logicalModemList.add(modemInfo2);
+        int[] deviceNrCapabilities = new int[0];
+
         DEFAULT_DSDS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false,
-                DEVICE_NR_CAPABILITY_NONE);
+                deviceNrCapabilities);
 
         logicalModemList = new ArrayList<>();
         logicalModemList.add(modemInfo1);
         DEFAULT_SSSS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false,
-                DEVICE_NR_CAPABILITY_NONE);
+                deviceNrCapabilities);
     }
 
     /**
-     * MaxActivePsVoice defines the maximum number of active voice calls.  For a dual sim dual
-     * standby (DSDS) modem it would be one, but for a dual sim dual active modem it would be 2.
+     * mMaxActiveVoiceSubscriptions defines the maximum subscriptions that can support
+     * simultaneous voice calls. For a dual sim dual standby (DSDS) device it would be one, but
+     * for a dual sim dual active device it would be 2.
      *
      * @hide
      */
-    private final int mMaxActivePsVoice;
+    private final int mMaxActiveVoiceSubscriptions;
 
     /**
-     * MaxActiveInternetData defines how many logical modems can have
-     * PS attached simultaneously. For example, for L+L modem it
-     * should be 2.
+     * mMaxActiveDataSubscriptions defines the maximum subscriptions that can support
+     * simultaneous data connections.
+     * For example, for L+L device it should be 2.
      *
      * @hide
      */
-    private final int mMaxActiveInternetData;
+    private final int mMaxActiveDataSubscriptions;
 
     /**
      * Whether modem supports both internet PDN up so
@@ -126,42 +122,45 @@
      *
      * @hide
      */
-    private final int mDeviceNrCapability;
+    private final int[] mDeviceNrCapabilities;
 
     /** @hide */
-    public PhoneCapability(int maxActivePsVoice, int maxActiveInternetData,
+    public PhoneCapability(int maxActiveVoiceSubscriptions, int maxActiveDataSubscriptions,
             List<ModemInfo> logicalModemList, boolean networkValidationBeforeSwitchSupported,
-            int deviceNrCapability) {
-        this.mMaxActivePsVoice = maxActivePsVoice;
-        this.mMaxActiveInternetData = maxActiveInternetData;
+            int[] deviceNrCapabilities) {
+        this.mMaxActiveVoiceSubscriptions = maxActiveVoiceSubscriptions;
+        this.mMaxActiveDataSubscriptions = maxActiveDataSubscriptions;
         // Make sure it's not null.
         this.mLogicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList;
         this.mNetworkValidationBeforeSwitchSupported = networkValidationBeforeSwitchSupported;
-        this.mDeviceNrCapability = deviceNrCapability;
+        this.mDeviceNrCapabilities = deviceNrCapabilities;
     }
 
     @Override
     public String toString() {
-        return "mMaxActivePsVoice=" + mMaxActivePsVoice
-                + " mMaxActiveInternetData=" + mMaxActiveInternetData
+        return "mMaxActiveVoiceSubscriptions=" + mMaxActiveVoiceSubscriptions
+                + " mMaxActiveDataSubscriptions=" + mMaxActiveDataSubscriptions
                 + " mNetworkValidationBeforeSwitchSupported="
                 + mNetworkValidationBeforeSwitchSupported
-                + " mDeviceNrCapability " + mDeviceNrCapability;
+                + " mDeviceNrCapability " + Arrays.toString(mDeviceNrCapabilities);
     }
 
     private PhoneCapability(Parcel in) {
-        mMaxActivePsVoice = in.readInt();
-        mMaxActiveInternetData = in.readInt();
+        mMaxActiveVoiceSubscriptions = in.readInt();
+        mMaxActiveDataSubscriptions = in.readInt();
         mNetworkValidationBeforeSwitchSupported = in.readBoolean();
         mLogicalModemList = new ArrayList<>();
         in.readList(mLogicalModemList, ModemInfo.class.getClassLoader());
-        mDeviceNrCapability = in.readInt();
+        mDeviceNrCapabilities = in.createIntArray();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMaxActivePsVoice, mMaxActiveInternetData, mLogicalModemList,
-                mNetworkValidationBeforeSwitchSupported, mDeviceNrCapability);
+        return Objects.hash(mMaxActiveVoiceSubscriptions,
+                mMaxActiveDataSubscriptions,
+                mLogicalModemList,
+                mNetworkValidationBeforeSwitchSupported,
+                Arrays.hashCode(mDeviceNrCapabilities));
     }
 
     @Override
@@ -176,12 +175,12 @@
 
         PhoneCapability s = (PhoneCapability) o;
 
-        return (mMaxActivePsVoice == s.mMaxActivePsVoice
-                && mMaxActiveInternetData == s.mMaxActiveInternetData
+        return (mMaxActiveVoiceSubscriptions == s.mMaxActiveVoiceSubscriptions
+                && mMaxActiveDataSubscriptions == s.mMaxActiveDataSubscriptions
                 && mNetworkValidationBeforeSwitchSupported
                 == s.mNetworkValidationBeforeSwitchSupported
                 && mLogicalModemList.equals(s.mLogicalModemList)
-                && mDeviceNrCapability == s.mDeviceNrCapability);
+                && Arrays.equals(mDeviceNrCapabilities, s.mDeviceNrCapabilities));
     }
 
     /**
@@ -195,11 +194,11 @@
      * {@link Parcelable#writeToParcel}
      */
     public void writeToParcel(@NonNull Parcel dest, @Parcelable.WriteFlags int flags) {
-        dest.writeInt(mMaxActivePsVoice);
-        dest.writeInt(mMaxActiveInternetData);
+        dest.writeInt(mMaxActiveVoiceSubscriptions);
+        dest.writeInt(mMaxActiveDataSubscriptions);
         dest.writeBoolean(mNetworkValidationBeforeSwitchSupported);
         dest.writeList(mLogicalModemList);
-        dest.writeInt(mDeviceNrCapability);
+        dest.writeIntArray(mDeviceNrCapabilities);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<PhoneCapability> CREATOR =
@@ -214,25 +213,24 @@
     };
 
     /**
-     * @return the maximum number of active packet-switched calls.  For a dual
-     * sim dual standby (DSDS) modem it would be one, but for a dual sim dual active modem it
+     * @return the maximum subscriptions that can support simultaneous voice calls. For a dual
+     * sim dual standby (DSDS) device it would be one, but for a dual sim dual active device it
      * would be 2.
      * @hide
      */
     @SystemApi
-    public @IntRange(from = 1) int getMaxActivePacketSwitchedVoiceCalls() {
-        return mMaxActivePsVoice;
+    public @IntRange(from = 1) int getMaxActiveVoiceSubscriptions() {
+        return mMaxActiveVoiceSubscriptions;
     }
 
     /**
-     * @return MaxActiveInternetData defines how many logical modems can have PS attached
-     * simultaneously.
-     * For example, for L+L modem it should be 2.
+     * @return the maximum subscriptions that can support simultaneous data connections.
+     * For example, for L+L device it should be 2.
      * @hide
      */
     @SystemApi
-    public @IntRange(from = 1) int getMaxActiveInternetData() {
-        return mMaxActiveInternetData;
+    public @IntRange(from = 1) int getMaxActiveDataSubscriptions() {
+        return mMaxActiveDataSubscriptions;
     }
 
     /**
@@ -254,13 +252,16 @@
     }
 
     /**
-     * Return the device's NR capability.
+     * Return List of the device's NR capability. If the device doesn't support NR capability,
+     * then this api return empty array.
+     * @see DEVICE_NR_CAPABILITY_NSA
+     * @see DEVICE_NR_CAPABILITY_SA
      *
-     * @return {@link DeviceNrCapability} the device's NR capability.
+     * @return List of the device's NR capability.
      * @hide
      */
     @SystemApi
-    public @DeviceNrCapability int getDeviceNrCapabilityBitmask() {
-        return mDeviceNrCapability;
+    public @NonNull @DeviceNrCapability int[] getDeviceNrCapabilities() {
+        return mDeviceNrCapabilities == null ? (new int[0]) : mDeviceNrCapabilities;
     }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index f110dae..2d06062 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -564,6 +564,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public int getDataRegState() {
         return mDataRegState;
     }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index cfb29f1..5a12865 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -300,9 +300,12 @@
      * @param data Message data.
      * @param isCdma Indicates weather the type of the SMS is CDMA.
      * @return An SmsMessage representing the message.
+     *
+     * @hide
      */
+    @SystemApi
     @Nullable
-    public static SmsMessage createSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
+    public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
         SmsMessageBase wrappedMessage;
 
         if (isCdma) {
@@ -318,23 +321,6 @@
     }
 
     /**
-     * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access
-     * Profile Specification v1.4.2 5.8.
-     * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages.
-     *
-     * @param data Message data.
-     * @param isCdma Indicates weather the type of the SMS is CDMA.
-     * @return An SmsMessage representing the message.
-     *
-     * @hide
-     */
-    @SystemApi
-    @Nullable
-    public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
-        return null;
-    }
-
-    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      *
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index a46621a..67fe783 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -56,6 +56,7 @@
 import android.provider.Telephony.SimInfo;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
+import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 
@@ -67,6 +68,11 @@
 import com.android.internal.util.Preconditions;
 import com.android.telephony.Rlog;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -612,9 +618,9 @@
     public static final int D2D_SHARING_ALL_CONTACTS = 1;
 
     /**
-     * Device status is shared with all starred contacts.
+     * Device status is shared with all selected contacts.
      */
-    public static final int D2D_SHARING_STARRED_CONTACTS = 2;
+    public static final int D2D_SHARING_SELECTED_CONTACTS = 2;
 
     /**
      * Device status is shared whenever possible.
@@ -627,7 +633,7 @@
             value = {
                     D2D_SHARING_DISABLED,
                     D2D_SHARING_ALL_CONTACTS,
-                    D2D_SHARING_STARRED_CONTACTS,
+                    D2D_SHARING_SELECTED_CONTACTS,
                     D2D_SHARING_ALL
             })
     public @interface DeviceToDeviceStatusSharingPreference {}
@@ -639,6 +645,13 @@
     public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING;
 
     /**
+     * TelephonyProvider column name for contacts information that allow device to device sharing.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS =
+            SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS;
+
+    /**
      * TelephonyProvider column name for the color of a SIM.
      * <P>Type: INTEGER (int)</P>
      */
@@ -2439,6 +2452,57 @@
     }
 
     /**
+     * Serialize list of contacts uri to string
+     * @hide
+     */
+    public static String serializeUriLists(List<Uri> uris) {
+        List<String> contacts = new ArrayList<>();
+        for (Uri uri : uris) {
+            contacts.add(uri.toString());
+        }
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(bos);
+            oos.writeObject(contacts);
+            oos.flush();
+            return Base64.encodeToString(bos.toByteArray(), Base64.DEFAULT);
+        } catch (IOException e) {
+            logd("serializeUriLists IO exception");
+        }
+        return "";
+    }
+
+    /**
+     * Return list of contacts uri corresponding to query result.
+     * @param subId Subscription Id of Subscription
+     * @param propKey Column name in SubscriptionInfo database
+     * @return list of contacts uri to be returned
+     * @hide
+     */
+    private static List<Uri> getContactsFromSubscriptionProperty(int subId, String propKey,
+            Context context) {
+        String result = getSubscriptionProperty(subId, propKey, context);
+        if (result != null) {
+            try {
+                byte[] b = Base64.decode(result, Base64.DEFAULT);
+                ByteArrayInputStream bis = new ByteArrayInputStream(b);
+                ObjectInputStream ois = new ObjectInputStream(bis);
+                List<String> contacts = ArrayList.class.cast(ois.readObject());
+                List<Uri> uris = new ArrayList<>();
+                for (String contact : contacts) {
+                    uris.add(Uri.parse(contact));
+                }
+                return uris;
+            } catch (IOException e) {
+                logd("getContactsFromSubscriptionProperty IO exception");
+            } catch (ClassNotFoundException e) {
+                logd("getContactsFromSubscriptionProperty ClassNotFound exception");
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    /**
      * Store properties associated with SubscriptionInfo in database
      * @param subId Subscription Id of Subscription
      * @param propKey Column name in SubscriptionInfo database
@@ -3443,6 +3507,40 @@
     }
 
     /**
+     * Set the list of contacts that allow device to device status sharing for a subscription ID.
+     * The setting app uses this method to indicate with whom they wish to share device to device
+     * status information.
+     * @param contacts The list of contacts that allow device to device status sharing
+     * @param subscriptionId The unique Subscription ID in database
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void setDeviceToDeviceStatusSharingContacts(@NonNull List<Uri> contacts,
+            int subscriptionId) {
+        String contactString = serializeUriLists(contacts);
+        if (VDBG) {
+            logd("[setDeviceToDeviceStatusSharingContacts] + contacts: " + contactString
+                    + " subId: " + subscriptionId);
+        }
+        setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+                (iSub)->iSub.setDeviceToDeviceStatusSharingContacts(serializeUriLists(contacts),
+                        subscriptionId));
+    }
+
+    /**
+     * Returns the list of contacts that allow device to device status sharing.
+     * @param subscriptionId Subscription id of subscription
+     * @return The list of contacts that allow device to device status sharing
+     */
+    public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(
+            int subscriptionId) {
+        if (VDBG) {
+            logd("[getDeviceToDeviceStatusSharingContacts] + subId: " + subscriptionId);
+        }
+        return getContactsFromSubscriptionProperty(subscriptionId,
+                D2D_STATUS_SHARING_SELECTED_CONTACTS, mContext);
+    }
+
+    /**
      * DO NOT USE.
      * This API is designed for features that are not finished at this point. Do not call this API.
      * @hide
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 2f89bfb..88c66ac 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -84,7 +84,7 @@
      * </ul>
      * One of the use case is that UX can show a different icon, for example, "5G+"
      */
-    public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+    public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
 
     @NetworkType
     private final  int mNetworkType;
@@ -186,7 +186,8 @@
             case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
             case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
             case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
-            case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
+            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_ADVANCED";
             default: return "UNKNOWN";
         }
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d3246ca..d2da51a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14951,6 +14951,13 @@
     public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING =
             "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
 
+    /**
+     * Indicates whether {@link #getNetworkSlicingConfiguration} is supported. See comments on
+     * respective methods for more information.
+     */
+    public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED =
+            "CAPABILITY_SLICING_CONFIG_SUPPORTED";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = "CAPABILITY_", value = {
@@ -14958,6 +14965,7 @@
             CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
             CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE,
             CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING,
+            CAPABILITY_SLICING_CONFIG_SUPPORTED,
     })
     public @interface RadioInterfaceCapability {}
 
@@ -15077,7 +15085,12 @@
      * DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in
      * order to undo the mitigations above it (i.e {@link
      * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link
-     * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}).
+     * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). </p>
+     *
+     * <p> In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of
+     * this API must also be listed in the device configuration as an authorized app in
+     * {@code packages/services/Telephony/res/values/config.xml} under the
+     * {@code thermal_mitigation_allowlisted_packages} key. </p>
      *
      * @param thermalMitigationRequest Thermal mitigation request. See {@link
      * ThermalMitigationRequest} for details.
@@ -15096,7 +15109,8 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest);
+                return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest,
+                        getOpPackageName());
             }
             throw new IllegalStateException("telephony service is null.");
         } catch (RemoteException ex) {
@@ -15603,9 +15617,15 @@
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * </ul>
      *
+     * This will be invalid if the device does not support
+     * android.telephony.TelephonyManager#CAPABILITY_SLICING_CONFIG_SUPPORTED.
+     *
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive the current slicing configuration.
      */
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED)
     @SuppressAutoDoc // No support for carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getNetworkSlicingConfiguration(
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 9493c76..6493772 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -302,4 +302,6 @@
     int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
 
     int setDeviceToDeviceStatusSharing(int sharing, int subId);
+
+    int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8ed9cff..46752b7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2250,10 +2250,12 @@
      *
      * @param subId the id of the subscription
      * @param thermalMitigationRequest holds the parameters necessary for the request.
+     * @param callingPackage the package name of the calling package.
      * @throws InvalidThermalMitigationRequestException if the parametes are invalid.
      */
     int sendThermalMitigationRequest(int subId,
-            in ThermalMitigationRequest thermalMitigationRequest);
+            in ThermalMitigationRequest thermalMitigationRequest,
+            String callingPackage);
 
     /**
      * get the Generic Bootstrapping Architecture authentication keys
@@ -2349,6 +2351,16 @@
     boolean getCarrierSingleRegistrationEnabled(int subId);
 
     /**
+     * Overrides the ims feature validation result
+     */
+    boolean setImsFeatureValidationOverride(int subId, String enabled);
+
+    /**
+     * Gets the ims feature validation override value
+     */
+    boolean getImsFeatureValidationOverride(int subId);
+
+    /**
      *  Return the mobile provisioning url that is used to launch a browser to allow users to manage
      *  their mobile plan.
      */
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index fd126ad..1e54093 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -19,6 +19,7 @@
 import android.os.Build
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.assertParcelSane
@@ -44,7 +45,13 @@
             setPartialConnectivityAcceptable(false)
             setUnvalidatedConnectivityAcceptable(true)
         }.build()
-        assertParcelSane(config, 12)
+        if (isAtLeastS()) {
+            // From S, the config will have 12 items
+            assertParcelSane(config, 12)
+        } else {
+            // For R or below, the config will have 10 items
+            assertParcelSane(config, 10)
+        }
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 9946515..e7718b5 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -313,7 +313,8 @@
         if (isAtLeastS()) {
             netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2));
             netCap.setUids(uids);
-        } else if (isAtLeastR()) {
+        }
+        if (isAtLeastR()) {
             netCap.setOwnerUid(123);
             netCap.setAdministratorUids(new int[] {5, 11});
         }
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 6fc605e..36f205b 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -64,6 +64,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.Process;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -219,8 +220,8 @@
         ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
 
         // register callback
-        when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
-                anyInt(), any(), nullable(String.class))).thenReturn(request);
+        when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+                anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
         manager.requestNetwork(request, callback, handler);
 
         // callback triggers
@@ -247,8 +248,8 @@
         ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
 
         // register callback
-        when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
-                anyInt(), any(), nullable(String.class))).thenReturn(req1);
+        when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+                anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
         manager.requestNetwork(req1, callback, handler);
 
         // callback triggers
@@ -265,8 +266,8 @@
         verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
 
         // callback can be registered again
-        when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
-                anyInt(), any(), nullable(String.class))).thenReturn(req2);
+        when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+                anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
         manager.requestNetwork(req2, callback, handler);
 
         // callback triggers
@@ -289,8 +290,8 @@
         info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
 
         when(mCtx.getApplicationInfo()).thenReturn(info);
-        when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(),
-                any(), nullable(String.class))).thenReturn(request);
+        when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
+                anyInt(), any(), nullable(String.class))).thenReturn(request);
 
         Handler handler = new Handler(Looper.getMainLooper());
         manager.requestNetwork(request, callback, handler);
@@ -357,34 +358,40 @@
         final NetworkCallback callback = new ConnectivityManager.NetworkCallback();
 
         manager.requestNetwork(request, callback);
-        verify(mService).requestNetwork(eq(request.networkCapabilities),
+        verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
                 eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
         // Verify that register network callback does not calls requestNetwork at all.
         manager.registerNetworkCallback(request, callback);
-        verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(),
-                anyInt(), any(), any());
+        verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
+                anyInt(), anyInt(), any(), any());
         verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
+        Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+
         manager.registerDefaultNetworkCallback(callback);
-        verify(mService).requestNetwork(eq(null),
+        verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
                 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
-        Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        manager.registerDefaultNetworkCallbackAsUid(42, callback, handler);
+        verify(mService).requestNetwork(eq(42), eq(null),
+                eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+                eq(testPkgName), eq(testAttributionTag));
+
         manager.requestBackgroundNetwork(request, handler, callback);
-        verify(mService).requestNetwork(eq(request.networkCapabilities),
+        verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
         manager.registerSystemDefaultNetworkCallback(callback, handler);
-        verify(mService).requestNetwork(eq(null),
+        verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
                 eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index db1334a..d82565a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -33,7 +33,6 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
-import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
@@ -1201,12 +1200,10 @@
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
 
-            final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
-                    : mMockVpn.getNetwork().getNetId();
-            verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+            verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
                     eq(toUidRangeStableParcels(uids)));
             verify(mMockNetd, never())
-                    .networkRemoveUidRanges(eq(expectedNetId), any());
+                    .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any());
             mAgentRegistered = true;
             updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
             mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -1448,6 +1445,23 @@
         });
     }
 
+    private interface ExceptionalRunnable {
+        void run() throws Exception;
+    }
+
+    private void withPermission(String permission, ExceptionalRunnable r) throws Exception {
+        if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+            r.run();
+            return;
+        }
+        try {
+            mServiceContext.setPermission(permission, PERMISSION_GRANTED);
+            r.run();
+        } finally {
+            mServiceContext.setPermission(permission, PERMISSION_DENIED);
+        }
+    }
+
     private static final int PRIMARY_USER = 0;
     private static final UidRange PRIMARY_UIDRANGE =
             UidRange.createForUser(UserHandle.of(PRIMARY_USER));
@@ -1556,7 +1570,7 @@
         doReturn(mNetworkStack).when(deps).getNetworkStack();
         doReturn(mSystemProperties).when(deps).getSystemProperties();
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
-        doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
+        doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any());
         doAnswer(inv -> {
             mPolicyTracker = new WrappedMultinetworkPolicyTracker(
                     inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -3811,8 +3825,9 @@
             NetworkCapabilities networkCapabilities = new NetworkCapabilities();
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
                     .setNetworkSpecifier(new MatchAllNetworkSpecifier());
-            mService.requestNetwork(networkCapabilities, NetworkRequest.Type.REQUEST.ordinal(),
-                    null, 0, null, ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
+            mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
+                    NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
+                    ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
                     mContext.getPackageName(), getAttributionTag());
         });
 
@@ -4041,7 +4056,7 @@
     }
 
     @Test
-    public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+    public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception {
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false /* validated */);
 
@@ -4050,12 +4065,19 @@
         assertThrows(SecurityException.class,
                 () -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
         callback.assertNoCallback();
+        assertThrows(SecurityException.class,
+                () -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler));
+        callback.assertNoCallback();
 
         mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
                 PERMISSION_GRANTED);
         mCm.registerSystemDefaultNetworkCallback(callback, handler);
         callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCm.unregisterNetworkCallback(callback);
+
+        mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(callback);
     }
 
     private void setCaptivePortalMode(int mode) {
@@ -7488,6 +7510,10 @@
         final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback();
         registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID);
 
+        final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallbackAsUid(VPN_UID, vpnDefaultCallbackAsUid,
+                new Handler(ConnectivityThread.getInstanceLooper()));
+
         final int uid = Process.myUid();
         final int userId = UserHandle.getUserId(uid);
         final ArrayList<String> allowList = new ArrayList<>();
@@ -7507,6 +7533,7 @@
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
         vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7520,6 +7547,7 @@
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -7535,6 +7563,7 @@
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
 
         // The following requires that the UID of this test package is greater than VPN_UID. This
         // is always true in practice because a plain AOSP build with no apps installed has almost
@@ -7556,6 +7585,7 @@
         defaultCallback.assertNoCallback();
         vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7577,6 +7607,7 @@
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7589,6 +7620,7 @@
         assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7604,6 +7636,7 @@
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7616,6 +7649,7 @@
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7629,6 +7663,7 @@
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7640,6 +7675,7 @@
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         vpnUidCallback.assertNoCallback();  // vpnUidCallback has NOT_VPN capability.
         vpnUidDefaultCallback.assertNoCallback();  // VPN does not apply to VPN_UID
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7652,12 +7688,14 @@
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
+        vpnDefaultCallbackAsUid.assertNoCallback();
         assertNull(mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
         mCm.unregisterNetworkCallback(vpnUidCallback);
         mCm.unregisterNetworkCallback(vpnUidDefaultCallback);
+        mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid);
     }
 
     private void setupLegacyLockdownVpn() {
@@ -9758,14 +9796,13 @@
                 exemptUidCaptor.capture());
         assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
 
-        final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
-                : mMockVpn.getNetwork().getNetId();
-
         if (add) {
-            inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+            inOrder.verify(mMockNetd, times(1))
+                    .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
                     eq(toUidRangeStableParcels(vpnRanges)));
         } else {
-            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId),
+            inOrder.verify(mMockNetd, times(1))
+                    .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()),
                     eq(toUidRangeStableParcels(vpnRanges)));
         }
 
@@ -9806,8 +9843,8 @@
         for (int reqTypeInt : invalidReqTypeInts) {
             assertThrows("Expect throws for invalid request type " + reqTypeInt,
                     IllegalArgumentException.class,
-                    () -> mService.requestNetwork(nc, reqTypeInt, null, 0, null,
-                            ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
+                    () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
+                            null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
                             mContext.getPackageName(), getAttributionTag())
             );
         }
@@ -10378,6 +10415,7 @@
         mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
         registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
                 TEST_WORK_PROFILE_APP_UID);
+        // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
         mServiceContext.setPermission(
                 Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
     }
@@ -10397,7 +10435,7 @@
     private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
             @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup)
             throws Exception {
-        final int testPackageNameUid = 123;
+        final int testPackageNameUid = TEST_PACKAGE_UID;
         final String testPackageName = "per.app.defaults.package";
         setupMultipleDefaultNetworksForOemNetworkPreferenceTest(
                 networkPrefToSetup, testPackageNameUid, testPackageName);
@@ -10533,6 +10571,11 @@
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+                mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+                        new Handler(ConnectivityThread.getInstanceLooper())));
+
         // Setup the test process to use networkPref for their default network.
         setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
 
@@ -10543,19 +10586,22 @@
                 null,
                 mEthernetNetworkAgent.getNetwork());
 
-        // At this point with a restricted network used, the available callback should trigger
+        // At this point with a restricted network used, the available callback should trigger.
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
                 mEthernetNetworkAgent.getNetwork());
+        otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the default network which should trigger a LOST callback.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
 
         // At this point, with no network is available, the lost callback should trigger
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+        otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
         mCm.unregisterNetworkCallback(defaultNetworkCallback);
+        mCm.unregisterNetworkCallback(otherUidDefaultCallback);
     }
 
     @Test
@@ -10573,6 +10619,11 @@
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+                mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+                        new Handler(ConnectivityThread.getInstanceLooper())));
+
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         // The active nai for the default is null at this point as this is a restricted network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
@@ -10584,15 +10635,19 @@
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
                 mEthernetNetworkAgent.getNetwork());
+        otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the default network which should trigger a LOST callback.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+        otherUidDefaultCallback.assertNoCallback();
 
         // At this point, with no network is available, the lost callback should trigger
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+        otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
         mCm.unregisterNetworkCallback(defaultNetworkCallback);
+        mCm.unregisterNetworkCallback(otherUidDefaultCallback);
     }
 
     @Test
@@ -10606,6 +10661,11 @@
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+                mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+                        new Handler(ConnectivityThread.getInstanceLooper())));
+
         // Setup a process different than the test process to use the default network. This means
         // that the defaultNetworkCallback won't be tracked by the per-app policy.
         setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref);
@@ -10621,6 +10681,9 @@
         defaultNetworkCallback.assertNoCallback();
         assertDefaultNetworkCapabilities(userId /* no networks */);
 
+        // The other UID does have access, and gets a callback.
+        otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+
         // Bring up unrestricted cellular. This should now satisfy the default network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
@@ -10628,25 +10691,31 @@
                 mEthernetNetworkAgent.getNetwork());
 
         // At this point with an unrestricted network used, the available callback should trigger
+        // The other UID is unaffected and remains on the paid network.
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
                 mCellNetworkAgent.getNetwork());
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
+        otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the per-app network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
 
-        // Since the callback didn't use the per-app network, no callback should fire.
+        // Since the callback didn't use the per-app network, only the other UID gets a callback.
+        // Because the preference specifies no fallback, it does not switch to cellular.
         defaultNetworkCallback.assertNoCallback();
+        otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
 
         // Now bring down the default network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
 
         // As this callback was tracking the default, this should now trigger.
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
         mCm.unregisterNetworkCallback(defaultNetworkCallback);
+        mCm.unregisterNetworkCallback(otherUidDefaultCallback);
     }
 
     /**
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8c5d1d6..97c65eb 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -22,7 +22,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -56,6 +58,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -80,6 +83,12 @@
 
     IpConnectivityMetrics mService;
     NetdEventListenerService mNetdListener;
+    final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .build();
+    final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .build();
 
     @Before
     public void setUp() {
@@ -263,14 +272,6 @@
         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
 
-        NetworkCapabilities ncWifi = new NetworkCapabilities();
-        NetworkCapabilities ncCell = new NetworkCapabilities();
-        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
-        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
-        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
         ApfStats apfStats = new ApfStats.Builder()
                 .setDurationMs(45000)
                 .setReceivedRas(10)
@@ -584,11 +585,21 @@
         return buffer.toString();
     }
 
-    void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
-        mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+    private void setCapabilities(int netId) {
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+        networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+                netId == 100 ? mNcWifi : mNcCell);
+    }
+
+    void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
+        setCapabilities(netId);
+        mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
     }
 
     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        setCapabilities(netId);
         mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..52975e4 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -23,8 +23,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
@@ -61,18 +63,16 @@
 
     NetdEventListenerService mService;
     ConnectivityManager mCm;
+    final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .build();
+    final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .build();
 
     @Before
     public void setUp() {
-        NetworkCapabilities ncWifi = new NetworkCapabilities();
-        NetworkCapabilities ncCell = new NetworkCapabilities();
-        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
         mCm = mock(ConnectivityManager.class);
-        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
-        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
         mService = new NetdEventListenerService(mCm);
     }
 
@@ -470,7 +470,16 @@
         assertEquals(want, got);
     }
 
+    private void setCapabilities(int netId) {
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+        networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+                netId == 100 ? mNcWifi : mNcCell);
+    }
+
     Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+        setCapabilities(netId);
         return new Thread(() -> {
             try {
                 mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
@@ -481,6 +490,7 @@
     }
 
     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        setCapabilities(netId);
         mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index dde77b0..2f3ee68 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -50,9 +50,7 @@
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -104,17 +102,6 @@
 
     NetworkNotificationManager mManager;
 
-
-    @BeforeClass
-    public static void setUpClass() {
-        Notification.DevFlags.sForceDefaults = true;
-    }
-
-    @AfterClass
-    public static void tearDownClass() {
-        Notification.DevFlags.sForceDefaults = false;
-    }
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 8932892..bcd6ed7 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -59,9 +59,10 @@
             "android.view.RoundedCornersTest",
             "android.view.WindowMetricsTest",
             "android.view.PendingInsetsControllerTest",
-            "android.app.WindowContextTest",
+            "android.window.WindowContextTest",
             "android.window.WindowMetricsHelperTest",
-            "android.app.activity.ActivityThreadTest"
+            "android.app.activity.ActivityThreadTest",
+            "android.window.WindowContextControllerTest"
     };
 
     public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7515971..516c206 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -204,7 +204,7 @@
                 new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
 
         cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
-        verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
+        verify(mMockStatusCallback).onStatusChanged(VCN_STATUS_CODE_ACTIVE);
 
         cbBinder.onGatewayConnectionError(
                 UNDERLYING_NETWORK_CAPABILITIES,
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a9d5822..babea36 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -19,6 +19,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -238,9 +240,14 @@
         doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO))
                 .when(mSubMgr)
                 .getSubscriptionsInGroup(any());
-        doReturn(isPrivileged)
+        doReturn(mTelMgr)
                 .when(mTelMgr)
-                .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+                .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+        doReturn(isPrivileged
+                        ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+                        : CARRIER_PRIVILEGE_STATUS_NO_ACCESS)
+                .when(mTelMgr)
+                .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME));
     }
 
     @Test
@@ -391,7 +398,7 @@
         mTestLooper.moveTimeForward(
                 VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
         mTestLooper.dispatchAll();
-        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
         final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
 
         // Verify that new instance was different, and the old one was torn down
@@ -492,7 +499,7 @@
         doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
 
         try {
-            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
             fail("Expected IllegalStateException exception for system server");
         } catch (IllegalStateException expected) {
         }
@@ -505,7 +512,7 @@
                 .getBinderCallingUid();
 
         try {
-            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
             fail("Expected security exception for non system user");
         } catch (SecurityException expected) {
         }
@@ -516,15 +523,24 @@
         setupMockedCarrierPrivilege(false);
 
         try {
-            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
             fail("Expected security exception for missing carrier privileges");
         } catch (SecurityException expected) {
         }
     }
 
     @Test
+    public void testClearVcnConfigMismatchedPackages() throws Exception {
+        try {
+            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage");
+            fail("Expected security exception due to mismatched packages");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
     public void testClearVcnConfig() throws Exception {
-        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
         assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
         verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
     }
@@ -535,7 +551,7 @@
         mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
         verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
 
-        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
 
         verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
     }
@@ -564,7 +580,7 @@
         verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
 
         // Verify Vcn is stopped if it was already started
-        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
         verify(vcnInstance).teardownAsynchronously();
     }
 
@@ -781,7 +797,7 @@
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
 
         verify(mMockPolicyListener).onPolicyChanged();
     }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 4fa63d4..c853fc5 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.doAnswer;
 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;
 
@@ -139,8 +140,7 @@
         mTestLooper.dispatchAll();
     }
 
-    @Test
-    public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+    private void verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(boolean isActive) {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
         startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
 
@@ -150,14 +150,27 @@
         final TelephonySubscriptionSnapshot updatedSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
 
+        mVcn.setIsActive(isActive);
+
         mVcn.updateSubscriptionSnapshot(updatedSnapshot);
         mTestLooper.dispatchAll();
 
         for (final VcnGatewayConnection gateway : gatewayConnections) {
-            verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
+            verify(gateway, isActive ? times(1) : never())
+                    .updateSubscriptionSnapshot(eq(updatedSnapshot));
         }
     }
 
+    @Test
+    public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+        verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(true /* isActive */);
+    }
+
+    @Test
+    public void testSubscriptionSnapshotUpdatesVcnGatewayConnectionsWhileInactive() {
+        verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(false /* isActive */);
+    }
+
     private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
         for (final int[] caps : TEST_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, caps);
@@ -187,7 +200,6 @@
             NetworkRequestListener requestListener,
             Set<VcnGatewayConnection> expectedGatewaysTornDown) {
         assertFalse(mVcn.isActive());
-        assertTrue(mVcn.getVcnGatewayConnections().isEmpty());
         for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) {
             verify(gatewayConnection).teardownAsynchronously();
         }
@@ -238,6 +250,51 @@
     }
 
     @Test
+    public void testGatewayQuitWhileInactive() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        final Set<VcnGatewayConnection> gatewayConnections =
+                new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
+
+        mVcn.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
+        statusCallback.onQuit();
+        mTestLooper.dispatchAll();
+
+        // Verify that the VCN requests the networkRequests be resent
+        assertEquals(1, mVcn.getVcnGatewayConnections().size());
+        verify(mVcnNetworkProvider, never()).resendAllRequests(requestListener);
+    }
+
+    @Test
+    public void testUpdateConfigReevaluatesGatewayConnections() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        startGatewaysAndGetGatewayConnections(requestListener);
+        assertEquals(2, mVcn.getVcnGatewayConnectionConfigMap().size());
+
+        // Create VcnConfig with only one VcnGatewayConnectionConfig so a gateway connection is torn
+        // down
+        final VcnGatewayConnectionConfig activeConfig =
+                VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[0]);
+        final VcnGatewayConnectionConfig removedConfig =
+                VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[1]);
+        final VcnConfig updatedConfig =
+                new VcnConfig.Builder(mContext).addGatewayConnectionConfig(activeConfig).build();
+
+        mVcn.updateConfig(updatedConfig);
+        mTestLooper.dispatchAll();
+
+        final VcnGatewayConnection activeGatewayConnection =
+                mVcn.getVcnGatewayConnectionConfigMap().get(activeConfig);
+        final VcnGatewayConnection removedGatewayConnection =
+                mVcn.getVcnGatewayConnectionConfigMap().get(removedConfig);
+        verify(activeGatewayConnection, never()).teardownAsynchronously();
+        verify(removedGatewayConnection).teardownAsynchronously();
+        verify(mVcnNetworkProvider).resendAllRequests(requestListener);
+    }
+
+    @Test
     public void testUpdateConfigExitsSafeMode() {
         final NetworkRequestListener requestListener = verifyAndGetRequestListener();
         final Set<VcnGatewayConnection> gatewayConnections =
@@ -261,8 +318,8 @@
         verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener));
         assertTrue(mVcn.isActive());
         for (final int[] caps : TEST_CAPS) {
-            // Expect each gateway connection created on initial startup, and again with new configs
-            verify(mDeps, times(2))
+            // Expect each gateway connection created only on initial startup
+            verify(mDeps)
                     .newVcnGatewayConnection(
                             eq(mVcnContext),
                             eq(TEST_SUB_GROUP),
@@ -271,4 +328,14 @@
                             any());
         }
     }
+
+    @Test
+    public void testIgnoreNetworkRequestWhileInactive() {
+        mVcn.setIsActive(false /* isActive */);
+
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        triggerVcnRequestListeners(requestListener);
+
+        verify(mDeps, never()).newVcnGatewayConnection(any(), any(), any(), any(), any());
+    }
 }
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index c64f4bc..da0571ba 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -101,6 +101,7 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
+    private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
     private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
     private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
     private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
@@ -114,6 +115,18 @@
     private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
 
     /**
+     * Interface used to listen country code event
+     */
+    public interface CountryCodeChangeListener {
+        /**
+         * Called when country code changed.
+         *
+         * @param countryCode A new country code which is 2-Character alphanumeric.
+         */
+        void onChanged(@NonNull String countryCode);
+    }
+
+    /**
      * Interface used when waiting for scans to be completed (with results).
      */
     public interface ScanEventCallback {
@@ -147,6 +160,46 @@
         void onPnoRequestFailed();
     }
 
+    /** @hide */
+    @VisibleForTesting
+    public class WificondEventHandler extends IWificondEventCallback.Stub {
+        private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder =
+                new HashMap<>();
+
+        /**
+         * Register CountryCodeChangeListener with pid.
+         *
+         * @param executor The Executor on which to execute the callbacks.
+         * @param listener listener for country code changed events.
+         */
+        public void registerCountryCodeChangeListener(Executor executor,
+                CountryCodeChangeListener listener) {
+            mCountryCodeChangeListenerHolder.put(listener, executor);
+        }
+
+        /**
+         * Unregister CountryCodeChangeListener with pid.
+         *
+         * @param listener listener which registered country code changed events.
+         */
+        public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) {
+            mCountryCodeChangeListenerHolder.remove(listener);
+        }
+
+        @Override
+        public void OnRegDomainChanged(String countryCode) {
+            Log.d(TAG, "OnRegDomainChanged " + countryCode);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCountryCodeChangeListenerHolder.forEach((listener, executor) -> {
+                    executor.execute(() -> listener.onChanged(countryCode));
+                });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
     private class ScanEventHandler extends IScanEvent.Stub {
         private Executor mExecutor;
         private ScanEventCallback mCallback;
@@ -347,6 +400,12 @@
         mWificond = wificond;
     }
 
+    /** @hide */
+    @VisibleForTesting
+    public WificondEventHandler getWificondEventHandler() {
+        return mWificondEventHandler;
+    }
+
     private class PnoScanEventHandler extends IPnoScanEvent.Stub {
         private Executor mExecutor;
         private ScanEventCallback mCallback;
@@ -574,6 +633,7 @@
         }
         try {
             mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
+            mWificond.registerWificondEventCallback(mWificondEventHandler);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register death notification for wificond");
             // The remote has already died.
@@ -1174,6 +1234,34 @@
     }
 
     /**
+     * Register the provided listener for country code event.
+     *
+     * @param executor The Executor on which to execute the callbacks.
+     * @param listener listener for country code changed events.
+     * @return true on success, false on failure.
+     */
+    public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CountryCodeChangeListener listener) {
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+        Log.d(TAG, "registerCountryCodeEventListener called");
+        mWificondEventHandler.registerCountryCodeChangeListener(executor, listener);
+        return true;
+    }
+
+
+    /**
+     * Unregister CountryCodeChangeListener with pid.
+     *
+     * @param listener listener which registered country code changed events.
+     */
+    public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) {
+        Log.d(TAG, "unregisterCountryCodeEventListener called");
+        mWificondEventHandler.unregisterCountryCodeChangeListener(listener);
+    }
+
+    /**
      * Register the provided callback handler for SoftAp events. The interface must first be created
      * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
      * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 4b03a49..98a0042 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -97,14 +98,20 @@
     @Mock
     private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
     @Mock
+    private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener;
+    @Mock
+    private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2;
+    @Mock
     private Context mContext;
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
     private WifiNl80211Manager mWificondControl;
+    private WifiNl80211Manager.WificondEventHandler mWificondEventHandler;
     private static final String TEST_INTERFACE_NAME = "test_wlan_if";
     private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
     private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+    private static final String TEST_COUNTRY_CODE = "US";
     private static final byte[] TEST_SSID =
             new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
     private static final byte[] TEST_PSK =
@@ -182,6 +189,7 @@
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
         mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+        mWificondEventHandler = mWificondControl.getWificondEventHandler();
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
                         mNormalScanCallback, mPnoScanCallback));
@@ -760,6 +768,28 @@
     }
 
     /**
+     * Ensures callback works after register CountryCodeChangeListener.
+     */
+    @Test
+    public void testCountryCodeChangeListenerInvocation() throws Exception {
+        assertTrue(mWificondControl.registerCountryCodeChangeListener(
+                Runnable::run, mCountryCodeChangeListener));
+        assertTrue(mWificondControl.registerCountryCodeChangeListener(
+                Runnable::run, mCountryCodeChangeListener2));
+
+        mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE);
+
+        reset(mCountryCodeChangeListener);
+        reset(mCountryCodeChangeListener2);
+        mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2);
+        mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE);
+    }
+
+    /**
      * Verifies registration and invocation of wificond death handler.
      */
     @Test