Merge "Create SettingsTransitionHelper" into sc-dev
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/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 001fdf0..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();
@@ -20578,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();
@@ -20600,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);
@@ -20715,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);
@@ -20752,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);
@@ -21306,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;
@@ -21332,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;
@@ -21349,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();
@@ -21963,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();
@@ -22168,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();
@@ -22200,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);
@@ -22221,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
@@ -22824,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();
@@ -22846,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;
@@ -24477,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 {
@@ -24510,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();
@@ -34918,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";
@@ -35550,6 +35559,8 @@
     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";
@@ -42345,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
   }
@@ -48097,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bc43060..d54526d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1838,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);
@@ -2445,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();
@@ -4449,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();
@@ -4464,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);
@@ -7782,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);
@@ -7794,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
@@ -7804,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;
@@ -8927,6 +8936,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";
@@ -14279,13 +14289,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 b61004b..746a0b6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1478,7 +1478,7 @@
 package android.media.metrics {
 
   public final class LogSessionId {
-    method @NonNull public String getStringId();
+    ctor public LogSessionId(@NonNull String);
   }
 
 }
@@ -2333,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 6472913..420ec08 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5283,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);
@@ -6219,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) {
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/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/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/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/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 c0189e2..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) {
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/Settings.java b/core/java/android/provider/Settings.java
index ca63fbd..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.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 73208d3..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
@@ -4581,6 +4590,17 @@
          * 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";
     }
 
     /**
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/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/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/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/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/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/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/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/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java
deleted file mode 100644
index 1cadf3b..0000000
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.metrics;
-
-import android.annotation.NonNull;
-
-/**
- * Interface for playback related components used by playback metrics.
- */
-public interface PlaybackComponent {
-
-    /**
-     * Sets the playback ID of the component.
-     */
-    void setPlaybackId(@NonNull String playbackId);
-
-    /**
-     * Gets playback ID.
-     */
-    @NonNull String getPlaybackId();
-}
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/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 20ff93f..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,
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 0826922..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;
@@ -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/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 27f7ee2..f50b018 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -711,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 =
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/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/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/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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d019d2..badffce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -38,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;
@@ -74,6 +73,7 @@
 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.logging.UiEventLogger;
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/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 63639ed..4c6e06f70 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);
         }
 
         /**
@@ -4565,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;
@@ -4840,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
@@ -4860,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);
@@ -6010,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);
     }
 
@@ -6054,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<>();
 
@@ -6363,6 +6454,58 @@
         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));
+        }
+    }
+
+    /**
+     * 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();
+
+        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
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 77cec78..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) {
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/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 bd9a520..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;
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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a8bdedf..764fa02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -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 {
@@ -15465,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);
+        }
     }
 
     /**
@@ -15618,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);
@@ -21188,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);
@@ -21746,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,
@@ -22263,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);
@@ -22326,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);
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/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 c99b01f..a8ca5b6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2708,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/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/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index bd3f99a..60d9ea2 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -100,6 +100,21 @@
     return (s & (Constants::blockSize - 1)) == 0;
 }
 
+static bool getEnforceReadLogsMaxIntervalForSystemDataLoaders() {
+    return android::base::GetBoolProperty("debug.incremental.enforce_readlogs_max_interval_for_"
+                                          "system_dataloaders",
+                                          false);
+}
+
+static Seconds getReadLogsMaxInterval() {
+    constexpr int limit = duration_cast<Seconds>(Constants::readLogsMaxInterval).count();
+    int readlogs_max_interval_secs =
+            std::min(limit,
+                     android::base::GetIntProperty<
+                             int>("debug.incremental.readlogs_max_interval_sec", limit));
+    return Seconds{readlogs_max_interval_secs};
+}
+
 template <base::LogSeverity level = base::ERROR>
 bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) {
     auto cstr = path::c_str(name);
@@ -711,7 +726,8 @@
         dataLoaderStub = ifs->dataLoaderStub;
     }
 
-    if (dataLoaderStub->isSystemDataLoader()) {
+    if (dataLoaderStub->isSystemDataLoader() &&
+        !getEnforceReadLogsMaxIntervalForSystemDataLoaders()) {
         // Readlogs from system dataloader (adb) can always be collected.
         ifs->startLoadingTs = TimePoint::max();
     } else {
@@ -719,7 +735,7 @@
         const auto startLoadingTs = mClock->now();
         ifs->startLoadingTs = startLoadingTs;
         // Setup a callback to disable the readlogs after max interval.
-        addTimedJob(*mTimedQueue, storageId, Constants::readLogsMaxInterval,
+        addTimedJob(*mTimedQueue, storageId, getReadLogsMaxInterval(),
                     [this, storageId, startLoadingTs]() {
                         const auto ifs = getIfs(storageId);
                         if (!ifs) {
@@ -807,7 +823,7 @@
     // Check installation time.
     const auto now = mClock->now();
     const auto startLoadingTs = ifs->startLoadingTs;
-    if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
+    if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) {
         LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
                    << storageId;
         return -EPERM;
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/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/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/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/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/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/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/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/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2af4117..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());
@@ -1573,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));
@@ -9799,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)));
         }
 
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/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/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/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