Merge "Fixing gradle complain on skipping constructor method mid-way" into sc-dev
diff --git a/Android.bp b/Android.bp
index dc92586..202e548 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1295,6 +1295,18 @@
     ],
 }
 
+python_binary_host {
+    name: "update_font_metadata",
+    defaults: ["base_default"],
+    main: "tools/fonts/update_font_metadata.py",
+    srcs: [
+        "tools/fonts/update_font_metadata.py",
+    ],
+    libs: [
+        "fontTools",
+    ],
+}
+
 filegroup {
     name: "framework-media-annotation-srcs",
     srcs: [
diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
index 9e519f7..1d94d7e 100644
--- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
@@ -178,7 +178,7 @@
             // For testing, just disable enforcement to avoid hooking up to compat framework
             ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
         }
-        val parser = ParsingPackageUtils(false, null, null,
+        val parser = ParsingPackageUtils(false, null, null, emptyList(),
             object : ParsingPackageUtils.Callback {
                 override fun hasFeature(feature: String) = true
 
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index b1394c1..a3b8013 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -143,11 +143,11 @@
     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 putDocuments(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
-    method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
-    method public void removeByQuery(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
-    method public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+    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>>);
     method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @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, @NonNull java.util.concurrent.Executor);
     method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
   }
 
@@ -216,7 +216,7 @@
 
   public class GlobalSearchSession implements java.io.Closeable {
     method public void close();
-    method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
+    method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
   }
 
   public class PackageIdentifier {
@@ -226,13 +226,13 @@
   }
 
   public final class PutDocumentsRequest {
-    method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getDocuments();
+    method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getGenericDocuments();
   }
 
   public static final class PutDocumentsRequest.Builder {
     ctor public PutDocumentsRequest.Builder();
-    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull android.app.appsearch.GenericDocument...);
-    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
+    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull android.app.appsearch.GenericDocument...);
+    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
     method @NonNull public android.app.appsearch.PutDocumentsRequest build();
   }
 
@@ -290,14 +290,14 @@
   }
 
   public final class SearchSpec {
+    method @NonNull public java.util.List<java.lang.String> getFilterNamespaces();
     method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
+    method @NonNull public java.util.List<java.lang.String> getFilterSchemas();
     method public int getMaxSnippetSize();
-    method @NonNull public java.util.List<java.lang.String> getNamespaces();
     method public int getOrder();
     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 @NonNull public java.util.List<java.lang.String> getSchemaTypes();
     method public int getSnippetCount();
     method public int getSnippetCountPerProperty();
     method public int getTermMatch();
@@ -316,14 +316,14 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec build();
     method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
@@ -344,8 +344,8 @@
 
   public static final class SetSchemaRequest.Builder {
     ctor public SetSchemaRequest.Builder();
-    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema...);
-    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull android.app.appsearch.AppSearchSchema...);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
     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.AppSearchSchema.Migrator);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 814800e..69d4e53 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -270,12 +270,12 @@
      *     they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
      * @throws RuntimeException If an error occurred during the execution.
      * @hide
-     * @deprecated use {@link AppSearchSession#putDocuments} instead.
+     * @deprecated use {@link AppSearchSession#put} instead.
      */
     public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) {
         // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
         // one big list.
-        List<GenericDocument> documents = request.getDocuments();
+        List<GenericDocument> documents = request.getGenericDocuments();
         List<Bundle> documentBundles = new ArrayList<>(documents.size());
         for (int i = 0; i < documents.size(); i++) {
             documentBundles.add(documents.get(i).getBundle());
@@ -330,7 +330,7 @@
                     DEFAULT_DATABASE_NAME,
                     request.getNamespace(),
                     uris,
-                    request.getProjectionsVisibleToPackagesInternal(),
+                    request.getProjectionsInternal(),
                     mContext.getUserId(),
                     new IAppSearchBatchResultCallback.Stub() {
                         public void onResult(AppSearchBatchResult result) {
@@ -465,7 +465,7 @@
      *     {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
      * @throws RuntimeException If an error occurred during the execution.
      * @hide
-     * @deprecated use {@link AppSearchSession#removeByUri} instead.
+     * @deprecated use {@link AppSearchSession#remove} instead.
      */
     public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) {
         List<String> uris = new ArrayList<>(request.getUris());
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 670f8b9..8723515 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -102,7 +102,7 @@
     }
 
     /**
-     * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+     * Sets the schema that will be used by documents provided to the {@link #put} method.
      *
      * <p>The schema provided here is compared to the stored copy of the schema previously supplied
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -268,7 +268,7 @@
      *                 {@link Throwable} if an unexpected internal error occurred in AppSearch
      *                 service.
      */
-    public void putDocuments(
+    public void put(
             @NonNull PutDocumentsRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BatchResultCallback<String, Void> callback) {
@@ -276,7 +276,7 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
-        List<GenericDocument> documents = request.getDocuments();
+        List<GenericDocument> documents = request.getGenericDocuments();
         List<Bundle> documentBundles = new ArrayList<>(documents.size());
         for (int i = 0; i < documents.size(); i++) {
             documentBundles.add(documents.get(i).getBundle());
@@ -327,7 +327,7 @@
                     mDatabaseName,
                     request.getNamespace(),
                     new ArrayList<>(request.getUris()),
-                    request.getProjectionsVisibleToPackagesInternal(),
+                    request.getProjectionsInternal(),
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         public void onResult(AppSearchBatchResult result) {
@@ -423,7 +423,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    public SearchResults query(
+    public SearchResults search(
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor) {
@@ -441,7 +441,7 @@
      * <p>A usage report represents an event in which a user interacted with or viewed a document.
      *
      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
-     * metrics for that particular document. These metrics are used for ordering {@link #query}
+     * metrics for that particular document. These metrics are used for ordering {@link #search}
      * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
      * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
      *
@@ -494,7 +494,7 @@
      *                 {@link Throwable} if an unexpected internal error occurred in AppSearch
      *                 service.
      */
-    public void removeByUri(
+    public void remove(
             @NonNull RemoveByUriRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BatchResultCallback<String, Void> callback) {
@@ -523,7 +523,8 @@
     /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
-     * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+     * {@link SearchSpec.Builder#addFilterNamespaces} and
+     * {@link SearchSpec.Builder#addFilterSchemas}.
      *
      * <p> An empty {@code queryExpression} matches all documents.
      *
@@ -539,7 +540,8 @@
      *                        the operation succeeds, the callback will be invoked with
      *                        {@code null}.
      */
-    public void removeByQuery(@NonNull String queryExpression,
+    public void remove(
+            @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<AppSearchResult<Void>> callback) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index 6bb8554..8651834 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -134,7 +134,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    public SearchResults query(
+    public SearchResults search(
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor) {
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 2e00ff2..e94b3b2 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -141,10 +141,6 @@
         }
 
         /** Adds a property to the given type. */
-        // TODO(b/171360120): MissingGetterMatchingBuilder expects a method called getPropertys, but
-        //  we provide the (correct) method getProperties. Once the bug referenced in this TODO is
-        //  fixed, remove this SuppressLint.
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 2f02808..4c11514 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -22,6 +22,7 @@
 import android.annotation.SuppressLint;
 import android.app.appsearch.util.BundleUtil;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -37,9 +38,9 @@
  *
  * <p>Documents are constructed via {@link GenericDocument.Builder}.
  *
- * @see AppSearchSession#putDocuments
+ * @see AppSearchSession#put
  * @see AppSearchSession#getByUri
- * @see AppSearchSession#query
+ * @see AppSearchSession#search
  */
 public class GenericDocument {
     private static final String TAG = "AppSearchGenericDocumen";
@@ -210,7 +211,7 @@
         Object property = mProperties.get(key);
         if (property instanceof ArrayList) {
             return getPropertyBytesArray(key);
-        } else if (property instanceof Bundle[]) {
+        } else if (property instanceof Parcelable[]) {
             return getPropertyDocumentArray(key);
         }
         return property;
@@ -436,7 +437,7 @@
     @Nullable
     public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
         Preconditions.checkNotNull(key);
-        Bundle[] bundles = getAndCastPropertyArray(key, Bundle[].class);
+        Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
         if (bundles == null || bundles.length == 0) {
             return null;
         }
@@ -446,7 +447,18 @@
                 Log.e(TAG, "The inner bundle is null at " + i + ", for key: " + key);
                 continue;
             }
-            documents[i] = new GenericDocument(bundles[i]);
+            if (!(bundles[i] instanceof Bundle)) {
+                Log.e(
+                        TAG,
+                        "The inner element at "
+                                + i
+                                + " is a "
+                                + bundles[i].getClass()
+                                + ", not a Bundle for key: "
+                                + key);
+                continue;
+            }
+            documents[i] = new GenericDocument((Bundle) bundles[i]);
         }
         return documents;
     }
@@ -574,8 +586,8 @@
          * @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
          *     schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
          *     inserting a document of this {@code schemaType} into the AppSearch index using {@link
-         *     AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link
-         *     AppSearchSession#putDocuments}.
+         *     AppSearchSession#put}. Otherwise, the document will be rejected by {@link
+         *     AppSearchSession#put}.
          */
         @SuppressWarnings("unchecked")
         public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -817,7 +829,7 @@
 
         private void putInPropertyBundle(@NonNull String key, @NonNull GenericDocument[] values) {
             validateRepeatedPropertyLength(key, values.length);
-            Bundle[] documentBundles = new Bundle[values.length];
+            Parcelable[] documentBundles = new Parcelable[values.length];
             for (int i = 0; i < values.length; i++) {
                 if (values[i] == null) {
                     throw new IllegalArgumentException("The document at " + i + " is null.");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 38e0046..0fcf061 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -96,7 +96,7 @@
      * @hide
      */
     @NonNull
-    public Map<String, List<String>> getProjectionsVisibleToPackagesInternal() {
+    public Map<String, List<String>> getProjectionsInternal() {
         return mTypePropertyPathsMap;
     }
 
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 0f141d6..05b2128 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -16,8 +16,8 @@
 
 package android.app.appsearch;
 
+
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 
 import com.android.internal.util.Preconditions;
 
@@ -41,7 +41,7 @@
 
     /** Returns the documents that are part of this request. */
     @NonNull
-    public List<GenericDocument> getDocuments() {
+    public List<GenericDocument> getGenericDocuments() {
         return Collections.unmodifiableList(mDocuments);
     }
 
@@ -55,17 +55,15 @@
         private boolean mBuilt = false;
 
         /** Adds one or more {@link GenericDocument} objects to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
-        public Builder addGenericDocument(@NonNull GenericDocument... documents) {
+        public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
             Preconditions.checkNotNull(documents);
-            return addGenericDocument(Arrays.asList(documents));
+            return addGenericDocuments(Arrays.asList(documents));
         }
 
         /** Adds a collection of {@link GenericDocument} objects to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
-        public Builder addGenericDocument(
+        public Builder addGenericDocuments(
                 @NonNull Collection<? extends GenericDocument> documents) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(documents);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index a04da34..2104198d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -29,7 +29,7 @@
 /**
  * Encapsulates a request to remove documents by namespace and URI.
  *
- * @see AppSearchSession#removeByUri
+ * @see AppSearchSession#remove
  */
 public final class RemoveByUriRequest {
     private final String mNamespace;
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 963062c..f72f8e1 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -19,7 +19,6 @@
 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;
@@ -49,7 +48,7 @@
     public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
 
     static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
-    static final String SCHEMA_TYPE_FIELD = "schemaType";
+    static final String SCHEMA_FIELD = "schema";
     static final String NAMESPACE_FIELD = "namespace";
     static final String PACKAGE_NAME_FIELD = "packageName";
     static final String NUM_PER_PAGE_FIELD = "numPerPage";
@@ -171,12 +170,12 @@
      * <p>If empty, the query will search over all schema types.
      */
     @NonNull
-    public List<String> getSchemaTypes() {
-        List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
-        if (schemaTypes == null) {
+    public List<String> getFilterSchemas() {
+        List<String> schemas = mBundle.getStringArrayList(SCHEMA_FIELD);
+        if (schemas == null) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableList(schemaTypes);
+        return Collections.unmodifiableList(schemas);
     }
 
     /**
@@ -185,7 +184,7 @@
      * <p>If empty, the query will search over all namespaces.
      */
     @NonNull
-    public List<String> getNamespaces() {
+    public List<String> getFilterNamespaces() {
         List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD);
         if (namespaces == null) {
             return Collections.emptyList();
@@ -209,24 +208,6 @@
         return Collections.unmodifiableList(packageNames);
     }
 
-    /**
-     * Returns the list of package names to search over.
-     *
-     * <p>If unset, the query will search over all packages that the caller has access to. If
-     * package names are specified which caller doesn't have access to, then those package names
-     * will be ignored.
-     *
-     * @hide
-     */
-    @NonNull
-    public List<String> getPackageNames() {
-        List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
-        if (packageNames == null) {
-            return Collections.emptyList();
-        }
-        return Collections.unmodifiableList(packageNames);
-    }
-
     /** Returns the number of results per page in the result set. */
     public int getResultCountPerPage() {
         return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
@@ -270,11 +251,10 @@
     @NonNull
     public Map<String, List<String>> getProjections() {
         Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
-        Set<String> schemaTypes = typePropertyPathsBundle.keySet();
-        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
-        for (String schemaType : schemaTypes) {
-            typePropertyPathsMap.put(
-                    schemaType, typePropertyPathsBundle.getStringArrayList(schemaType));
+        Set<String> schemas = typePropertyPathsBundle.keySet();
+        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
+        for (String schema : schemas) {
+            typePropertyPathsMap.put(schema, typePropertyPathsBundle.getStringArrayList(schema));
         }
         return typePropertyPathsMap;
     }
@@ -283,7 +263,7 @@
     public static final class Builder {
 
         private final Bundle mBundle;
-        private final ArrayList<String> mSchemaTypes = new ArrayList<>();
+        private final ArrayList<String> mSchemas = new ArrayList<>();
         private final ArrayList<String> mNamespaces = new ArrayList<>();
         private final ArrayList<String> mPackageNames = new ArrayList<>();
         private final Bundle mProjectionTypePropertyMasks = new Bundle();
@@ -312,10 +292,10 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchemaType(@NonNull String... schemaTypes) {
-            Preconditions.checkNotNull(schemaTypes);
+        public Builder addFilterSchemas(@NonNull String... schemas) {
+            Preconditions.checkNotNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            return addSchemaType(Arrays.asList(schemaTypes));
+            return addFilterSchemas(Arrays.asList(schemas));
         }
 
         /**
@@ -325,10 +305,10 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
-            Preconditions.checkNotNull(schemaTypes);
+        public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
+            Preconditions.checkNotNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mSchemaTypes.addAll(schemaTypes);
+            mSchemas.addAll(schemas);
             return this;
         }
 
@@ -339,10 +319,10 @@
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
-        public Builder addNamespace(@NonNull String... namespaces) {
+        public Builder addFilterNamespaces(@NonNull String... namespaces) {
             Preconditions.checkNotNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            return addNamespace(Arrays.asList(namespaces));
+            return addFilterNamespaces(Arrays.asList(namespaces));
         }
 
         /**
@@ -352,7 +332,7 @@
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
-        public Builder addNamespace(@NonNull Collection<String> namespaces) {
+        public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
             Preconditions.checkNotNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mNamespaces.addAll(namespaces);
@@ -367,9 +347,6 @@
          * package names are specified which caller doesn't have access to, then those package names
          * will be ignored.
          */
-        // Getter is called "getFilterPackageNames" (as opposed to the suggested
-        // "getFilterPackageNameses")
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public Builder addFilterPackageNames(@NonNull String... packageNames) {
             Preconditions.checkNotNull(packageNames);
@@ -385,9 +362,6 @@
          * package names are specified which caller doesn't have access to, then those package names
          * will be ignored.
          */
-        // Getter is called "getFilterPackageNames" (as opposed to the suggested
-        // "getFilterPackageNameses")
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
             Preconditions.checkNotNull(packageNames);
@@ -537,7 +511,7 @@
          * type property paths:
          *
          * <pre>{@code
-         * {schemaType: "Email", ["subject", "sender.name", "recipients.name"]}
+         * {schema: "Email", ["subject", "sender.name", "recipients.name"]}
          * }</pre>
          *
          * <p>The above document will be returned as:
@@ -561,9 +535,9 @@
          */
         @NonNull
         public SearchSpec.Builder addProjection(
-                @NonNull String schemaType, @NonNull String... propertyPaths) {
+                @NonNull String schema, @NonNull String... propertyPaths) {
             Preconditions.checkNotNull(propertyPaths);
-            return addProjection(schemaType, Arrays.asList(propertyPaths));
+            return addProjection(schema, Arrays.asList(propertyPaths));
         }
 
         /**
@@ -583,16 +557,16 @@
          */
         @NonNull
         public SearchSpec.Builder addProjection(
-                @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+                @NonNull String schema, @NonNull Collection<String> propertyPaths) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schemaType);
+            Preconditions.checkNotNull(schema);
             Preconditions.checkNotNull(propertyPaths);
             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
                 Preconditions.checkNotNull(propertyPath);
                 propertyPathsArrayList.add(propertyPath);
             }
-            mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList);
+            mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
             return this;
         }
 
@@ -608,7 +582,7 @@
                 throw new IllegalSearchSpecException("Missing termMatchType field.");
             }
             mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
-            mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
+            mBundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
             mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
             mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
             mBuilt = true;
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 1486df3..2caa94a5e 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -126,9 +126,9 @@
          * <p>Any documents of these types will be visible on system UI surfaces by default.
          */
         @NonNull
-        public Builder addSchema(@NonNull AppSearchSchema... schemas) {
+        public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
             Preconditions.checkNotNull(schemas);
-            return addSchema(Arrays.asList(schemas));
+            return addSchemas(Arrays.asList(schemas));
         }
 
         /**
@@ -137,7 +137,7 @@
          * <p>Any documents of these types will be visible on system UI surfaces by default.
          */
         @NonNull
-        public Builder addSchema(@NonNull Collection<AppSearchSchema> schemas) {
+        public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(schemas);
             mSchemas.addAll(schemas);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 90a6f60..0328d54 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -179,9 +179,9 @@
 
         /** Adds migratedTypes to the list of migrated schema types. */
         @NonNull
-        public Builder addMigratedType(@NonNull String migratedType) {
+        public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
+            mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
             return this;
         }
 
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 592b8b9..8bff720 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
@@ -195,8 +195,7 @@
             mIcingSearchEngineLocked = new IcingSearchEngine(options);
 
             mVisibilityStoreLocked =
-                    new VisibilityStore(
-                            this, context, userId, globalQuerierPackage);
+                    new VisibilityStore(this, context, userId, globalQuerierPackage);
 
             InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
             SchemaProto schemaProto;
@@ -508,8 +507,8 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        if (!searchSpec.getPackageNames().isEmpty()
-                && !searchSpec.getPackageNames().contains(packageName)) {
+        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
             // Client wanted to query over some packages that weren't its own. This isn't
             // allowed through local query so we can return early with no results.
             return new SearchResultPage(Bundle.EMPTY);
@@ -553,7 +552,7 @@
             throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
-            Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames());
+            Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
             Set<String> prefixFilters = new ArraySet<>();
             Set<String> allPrefixes = mNamespaceMapLocked.keySet();
             if (packageFilters.isEmpty()) {
@@ -573,7 +572,7 @@
 
             // Find which schemas the client is allowed to query over.
             Set<String> allowedPrefixedSchemas = new ArraySet<>();
-            List<String> schemaFilters = searchSpec.getSchemaTypes();
+            List<String> schemaFilters = searchSpec.getFilterSchemas();
             for (String prefix : prefixFilters) {
                 String packageName = getPackageName(prefix);
 
@@ -752,8 +751,8 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        if (!searchSpec.getPackageNames().isEmpty()
-                && !searchSpec.getPackageNames().contains(packageName)) {
+        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
             // We're only removing documents within the parameter `packageName`. If we're not
             // restricting our remove-query to this package name, then there's nothing for us to
             // remove.
@@ -1085,7 +1084,7 @@
         Set<String> allowedPrefixedSchemas = new ArraySet<>();
 
         // Add all the schema filters the client specified.
-        List<String> schemaFilters = searchSpec.getSchemaTypes();
+        List<String> schemaFilters = searchSpec.getFilterSchemas();
         for (int i = 0; i < schemaFilters.size(); i++) {
             allowedPrefixedSchemas.add(prefix + schemaFilters.get(i));
         }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
new file mode 100644
index 0000000..b3f8264
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchMigrationHelper;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResultPage;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.android.internal.util.Preconditions;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * An implementation of {@link AppSearchMigrationHelper} which query document and save post-migrated
+ * documents to locally in the app's storage space.
+ */
+class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
+    private final AppSearchImpl mAppSearchImpl;
+    private final String mPackageName;
+    private final String mDatabaseName;
+    private final File mFile;
+    private final Map<String, Integer> mCurrentVersionMap;
+    private final Map<String, Integer> mFinalVersionMap;
+
+    AppSearchMigrationHelperImpl(
+            @NonNull AppSearchImpl appSearchImpl,
+            @NonNull Map<String, Integer> currentVersionMap,
+            @NonNull Map<String, Integer> finalVersionMap,
+            @NonNull String packageName,
+            @NonNull String databaseName)
+            throws IOException {
+        mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
+        mCurrentVersionMap = Preconditions.checkNotNull(currentVersionMap);
+        mFinalVersionMap = Preconditions.checkNotNull(finalVersionMap);
+        mPackageName = Preconditions.checkNotNull(packageName);
+        mDatabaseName = Preconditions.checkNotNull(databaseName);
+        mFile = File.createTempFile(/*prefix=*/ "appsearch", /*suffix=*/ null);
+    }
+
+    @Override
+    public void queryAndTransform(
+            @NonNull String schemaType, @NonNull AppSearchMigrationHelper.Transformer migrator)
+            throws Exception {
+        Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+        int currentVersion = mCurrentVersionMap.get(schemaType);
+        int finalVersion = mFinalVersionMap.get(schemaType);
+        try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
+            // TODO(b/151178558) change the output stream so that we can use it in platform
+            CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
+            SearchResultPage searchResultPage =
+                    mAppSearchImpl.query(
+                            mPackageName,
+                            mDatabaseName,
+                            /*queryExpression=*/ "",
+                            new SearchSpec.Builder()
+                                    .addFilterSchemas(schemaType)
+                                    .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                    .build());
+            while (!searchResultPage.getResults().isEmpty()) {
+                for (int i = 0; i < searchResultPage.getResults().size(); i++) {
+                    GenericDocument newDocument =
+                            migrator.transform(
+                                    currentVersion,
+                                    finalVersion,
+                                    searchResultPage.getResults().get(i).getDocument());
+                    Bundle bundle = newDocument.getBundle();
+                    Parcel parcel = Parcel.obtain();
+                    parcel.writeBundle(bundle);
+                    byte[] serializedMessage = parcel.marshall();
+                    parcel.recycle();
+                    codedOutputStream.writeByteArrayNoTag(serializedMessage);
+                }
+                codedOutputStream.flush();
+                searchResultPage = mAppSearchImpl.getNextPage(searchResultPage.getNextPageToken());
+                outputStream.flush();
+            }
+        }
+    }
+
+    /**
+     * Reads {@link GenericDocument} from the temperate file and saves them to AppSearch.
+     *
+     * <p>This method should be only called once.
+     *
+     * @return the {@link AppSearchBatchResult} for migration documents.
+     */
+    @NonNull
+    public SetSchemaResponse readAndPutDocuments(SetSchemaResponse.Builder responseBuilder)
+            throws IOException, AppSearchException {
+        Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+        try (InputStream inputStream = new FileInputStream(mFile)) {
+            CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream);
+            while (!codedInputStream.isAtEnd()) {
+                GenericDocument document = readDocumentFromInputStream(codedInputStream);
+                try {
+                    mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
+                } catch (Throwable t) {
+                    responseBuilder.setFailure(
+                            document.getSchemaType(),
+                            document.getNamespace(),
+                            document.getUri(),
+                            throwableToFailedResult(t));
+                }
+            }
+            mAppSearchImpl.persistToDisk();
+            return responseBuilder.build();
+        } finally {
+            mFile.delete();
+        }
+    }
+
+    void deleteTempFile() {
+        mFile.delete();
+    }
+
+    /**
+     * Reads {@link GenericDocument} from given {@link CodedInputStream}.
+     *
+     * @param codedInputStream The codedInputStream to read from
+     * @throws IOException on File operation error.
+     */
+    @NonNull
+    private static GenericDocument readDocumentFromInputStream(
+            @NonNull CodedInputStream codedInputStream) throws IOException {
+        byte[] serializedMessage = codedInputStream.readByteArray();
+
+        Parcel parcel = Parcel.obtain();
+        parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
+        parcel.setDataPosition(0);
+        Bundle bundle = parcel.readBundle();
+        parcel.recycle();
+
+        return new GenericDocument(bundle);
+    }
+}
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 07d50ae..3b5e275 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
@@ -40,8 +40,8 @@
         Preconditions.checkNotNull(spec);
         SearchSpecProto.Builder protoBuilder =
                 SearchSpecProto.newBuilder()
-                        .addAllSchemaTypeFilters(spec.getSchemaTypes())
-                        .addAllNamespaceFilters(spec.getNamespaces());
+                        .addAllSchemaTypeFilters(spec.getFilterSchemas())
+                        .addAllNamespaceFilters(spec.getFilterNamespaces());
 
         @SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
         TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 2774181..12699b7 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I3fd4c96bf775c2539d744c416cdbf1d3c9544f03
+Ibe06fb9c574c8718191f833bb042fa10c300e4e2
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 f8d0d80..afa633a 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
@@ -102,10 +102,10 @@
 
     @Override
     @NonNull
-    public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
+    public ListenableFuture<AppSearchBatchResult<String, Void>> put(
             @NonNull PutDocumentsRequest request) {
         SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
-        mAppSearchSession.putDocuments(
+        mAppSearchSession.put(
                 request, mExecutor, new BatchResultCallbackAdapter<>(future));
         return future;
     }
@@ -122,10 +122,10 @@
 
     @Override
     @NonNull
-    public SearchResultsShim query(
+    public SearchResultsShim search(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SearchResults searchResults =
-                mAppSearchSession.query(queryExpression, searchSpec, mExecutor);
+                mAppSearchSession.search(queryExpression, searchSpec, mExecutor);
         return new SearchResultsShimImpl(searchResults, mExecutor);
     }
 
@@ -139,19 +139,19 @@
 
     @Override
     @NonNull
-    public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+    public ListenableFuture<AppSearchBatchResult<String, Void>> remove(
             @NonNull RemoveByUriRequest request) {
         SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
-        mAppSearchSession.removeByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future));
+        mAppSearchSession.remove(request, mExecutor, new BatchResultCallbackAdapter<>(future));
         return future;
     }
 
     @Override
     @NonNull
-    public ListenableFuture<Void> removeByQuery(
+    public ListenableFuture<Void> remove(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
-        mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set);
+        mAppSearchSession.remove(queryExpression, searchSpec, mExecutor, future::set);
         return Futures.transformAsync(future, this::transformResult, mExecutor);
     }
 
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 39ca687..eb1623e 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
@@ -67,10 +67,10 @@
 
     @NonNull
     @Override
-    public SearchResultsShim query(
+    public SearchResultsShim search(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SearchResults searchResults =
-                mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
+                mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor);
         return new SearchResultsShimImpl(searchResults, mExecutor);
     }
 
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 e8ea6ef..8e62c0e 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
@@ -33,7 +33,7 @@
 public interface AppSearchSessionShim extends Closeable {
 
     /**
-     * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+     * Sets the schema that will be used by documents provided to the {@link #put} method.
      *
      * <p>The schema provided here is compared to the stored copy of the schema previously supplied
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -143,8 +143,7 @@
      *     they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
      */
     @NonNull
-    ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
-            @NonNull PutDocumentsRequest request);
+    ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
 
     /**
      * Retrieves {@link GenericDocument}s by URI.
@@ -161,7 +160,7 @@
             @NonNull GetByUriRequest request);
 
     /**
-     * Searches a document based on a given query string.
+     * Searches for documents based on a given query string.
      *
      * <p>Currently we support following features in the raw query format:
      *
@@ -200,7 +199,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /**
      * Reports usage of a particular document by URI and namespace.
@@ -208,7 +207,7 @@
      * <p>A usage report represents an event in which a user interacted with or viewed a document.
      *
      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
-     * metrics for that particular document. These metrics are used for ordering {@link #query}
+     * metrics for that particular document. These metrics are used for ordering {@link #search}
      * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
      * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
      *
@@ -231,13 +230,13 @@
      *     {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
      */
     @NonNull
-    ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+    ListenableFuture<AppSearchBatchResult<String, Void>> remove(
             @NonNull RemoveByUriRequest request);
 
     /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
-     * SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+     * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
      *
      * <p>An empty {@code queryExpression} matches all documents.
      *
@@ -251,8 +250,7 @@
      * @return The pending result of performing this operation.
      */
     @NonNull
-    ListenableFuture<Void> removeByQuery(
-            @NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /**
      * Flush all schema and document updates, additions, and deletes to disk if possible.
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 cd867a4..b96f99e 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
@@ -23,7 +23,7 @@
 /**
  * This class provides global access to the centralized AppSearch index maintained by the system.
  *
- * <p>Apps can retrieve indexed documents through the query API.
+ * <p>Apps can retrieve indexed documents through the {@link #search} API.
  */
 public interface GlobalSearchSessionShim extends Closeable {
     /**
@@ -66,7 +66,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /** Closes the {@link GlobalSearchSessionShim}. */
     @Override
diff --git a/core/api/current.txt b/core/api/current.txt
index 8dae210..d60bf2c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -657,7 +657,7 @@
     field @Deprecated public static final int fontProviderCerts = 16844125; // 0x101055d
     field @Deprecated public static final int fontProviderPackage = 16844119; // 0x1010557
     field @Deprecated public static final int fontProviderQuery = 16844113; // 0x1010551
-    field public static final int fontProviderSystemFontFamily = 16844322; // 0x1010622
+    field public static final int fontProviderSystemFontFamily = 16844321; // 0x1010621
     field public static final int fontStyle = 16844095; // 0x101053f
     field public static final int fontVariationSettings = 16844144; // 0x1010570
     field public static final int fontWeight = 16844083; // 0x1010533
@@ -722,7 +722,7 @@
     field public static final int gwpAsanMode = 16844310; // 0x1010616
     field public static final int hand_hour = 16843011; // 0x1010103
     field public static final int hand_minute = 16843012; // 0x1010104
-    field public static final int hand_second = 16844323; // 0x1010623
+    field public static final int hand_second = 16844322; // 0x1010622
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
@@ -1057,11 +1057,11 @@
     field public static final int parentActivityName = 16843687; // 0x10103a7
     field @Deprecated public static final int password = 16843100; // 0x101015c
     field public static final int path = 16842794; // 0x101002a
-    field public static final int pathAdvancedPattern = 16844320; // 0x1010620
+    field public static final int pathAdvancedPattern = 16844319; // 0x101061f
     field public static final int pathData = 16843781; // 0x1010405
     field public static final int pathPattern = 16842796; // 0x101002c
     field public static final int pathPrefix = 16842795; // 0x101002b
-    field public static final int pathSuffix = 16844318; // 0x101061e
+    field public static final int pathSuffix = 16844317; // 0x101061d
     field public static final int patternPathData = 16843978; // 0x10104ca
     field public static final int permission = 16842758; // 0x1010006
     field public static final int permissionFlags = 16843719; // 0x10103c7
@@ -1154,7 +1154,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 requireDeviceScreenOn = 16844317; // 0x101061d
+    field public static final int requireDeviceScreenOn = 16844316; // 0x101061c
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
     field public static final int requiredAccountType = 16843734; // 0x10103d6
@@ -1295,10 +1295,10 @@
     field public static final int spotShadowAlpha = 16843967; // 0x10104bf
     field public static final int src = 16843033; // 0x1010119
     field public static final int ssp = 16843747; // 0x10103e3
-    field public static final int sspAdvancedPattern = 16844321; // 0x1010621
+    field public static final int sspAdvancedPattern = 16844320; // 0x1010620
     field public static final int sspPattern = 16843749; // 0x10103e5
     field public static final int sspPrefix = 16843748; // 0x10103e4
-    field public static final int sspSuffix = 16844319; // 0x101061f
+    field public static final int sspSuffix = 16844318; // 0x101061e
     field public static final int stackFromBottom = 16843005; // 0x10100fd
     field public static final int stackViewStyle = 16843838; // 0x101043e
     field public static final int starStyle = 16842882; // 0x1010082
@@ -21108,7 +21108,9 @@
 
   public static final class MediaCodecInfo.AudioCapabilities {
     method public android.util.Range<java.lang.Integer> getBitrateRange();
+    method @NonNull public android.util.Range<java.lang.Integer>[] getInputChannelCountRanges();
     method public int getMaxInputChannelCount();
+    method public int getMinInputChannelCount();
     method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
     method public int[] getSupportedSampleRates();
     method public boolean isSampleRateSupported(int);
@@ -49586,6 +49588,7 @@
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
+    field @Deprecated public static final int FLAG_BLUR_BEHIND = 4; // 0x4
     field public static final int FLAG_DIM_BEHIND = 2; // 0x2
     field @Deprecated public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
     field @Deprecated public static final int FLAG_DITHER = 4096; // 0x1000
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e8650fa..be3c246 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -208,6 +208,16 @@
     method public void teardownTestNetwork(@NonNull android.net.Network);
   }
 
+  public final class UnderlyingNetworkInfo implements android.os.Parcelable {
+    ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.UnderlyingNetworkInfo> CREATOR;
+    field @NonNull public final String iface;
+    field public final int ownerUid;
+    field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
+  }
+
 }
 
 package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 683bc84..4359495 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -263,7 +263,6 @@
     field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
-    field public static final String USE_BACKGROUND_BLUR = "android.permission.USE_BACKGROUND_BLUR";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -295,8 +294,6 @@
     field public static final int sdkVersion = 16844304; // 0x1010610
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
-    field public static final int windowBackgroundBlurEnabled = 16844316; // 0x101061c
-    field public static final int windowBackgroundBlurRadius = 16844315; // 0x101061b
   }
 
   public static final class R.bool {
@@ -2759,18 +2756,6 @@
 
 }
 
-package android.graphics.drawable {
-
-  public final class BackgroundBlurDrawable extends android.graphics.drawable.Drawable {
-    ctor @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public BackgroundBlurDrawable();
-    method public void setBlurRadius(int);
-    method public void setColor(@ColorInt int);
-    method public void setCornerRadius(float);
-    method public void setCornerRadius(float, float, float, float);
-  }
-
-}
-
 package android.graphics.fonts {
 
   public class FontManager {
@@ -14102,10 +14087,8 @@
     method public boolean isSystemApplicationOverlay();
     method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
     method public final void setUserActivityTimeout(long);
-    field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public static final int FLAG_BLUR_BEHIND = 4; // 0x4
     field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
     field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
-    field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public int backgroundBlurRadius;
   }
 
   @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7e7f887..b51d4ac 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -59,6 +59,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -70,6 +71,12 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -116,6 +123,7 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.File;
 import java.lang.ref.WeakReference;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
@@ -2055,6 +2063,31 @@
         return info.loadLabel(this);
     }
 
+    @Nullable
+    public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
+            @PackageInfoFlags int flags) {
+        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
+            // Caller expressed no opinion about what encryption
+            // aware/unaware components they want to see, so match both
+            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        }
+
+        boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
+                || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
+
+        ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
+        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
+                new File(archiveFilePath), 0, getPermissionManager().getSplitPermissions(),
+                collectCertificates);
+        if (result.isError()) {
+            return null;
+        }
+        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
+                new PackageUserState(), UserHandle.getCallingUserId());
+    }
+
     @Override
     public int installExistingPackage(String packageName) throws NameNotFoundException {
         return installExistingPackage(packageName, INSTALL_REASON_UNKNOWN);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index c2c62c1..9d3286f 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -96,7 +96,14 @@
     oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
     oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
     oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
-    oneway void overridePendingTransition(in IBinder token, in String packageName,
+    /**
+     * Overrides the animation of activity pending transition. This call is not one-way because
+     * the method is usually used after startActivity or Activity#finish. If this is non-blocking,
+     * the calling activity may proceed to complete pause and become stopping state, which will
+     * cause the request to be ignored. Besides, startActivity and Activity#finish are blocking
+     * calls, so this method should be the same as them to keep the invocation order.
+     */
+    void overridePendingTransition(in IBinder token, in String packageName,
             int enterAnim, int exitAnim);
     int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
 
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index e31e024..390d921 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -337,6 +337,23 @@
     }
 
     /**
+     * @return {@code true} if parameters that are important for size compat have changed.
+     * @hide
+     */
+    public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
+        if (that == null) {
+            return false;
+        }
+        return displayId == that.displayId
+                && taskId == that.taskId
+                && topActivityInSizeCompat == that.topActivityInSizeCompat
+                // TopActivityToken and bounds are important if top activity is in size compat
+                && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
+                && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
+                    .equals(that.configuration.windowConfiguration.getBounds()));
+    }
+
+    /**
      * Reads the TaskInfo from a parcel.
      */
     void readFromParcel(Parcel source) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 72fb1ca..e01e530 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -48,12 +48,6 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.dex.ArtManager;
-import android.content.pm.parsing.PackageInfoWithoutStateUtils;
-import android.content.pm.parsing.ParsingPackage;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.result.ParseInput;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -83,7 +77,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.cert.Certificate;
@@ -6791,25 +6784,8 @@
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
             @PackageInfoFlags int flags) {
-        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
-            // Caller expressed no opinion about what encryption
-            // aware/unaware components they want to see, so match both
-            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
-                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-        }
-
-        boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
-                || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
-
-        ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
-        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
-                new File(archiveFilePath), 0, collectCertificates);
-        if (result.isError()) {
-            return null;
-        }
-        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
-                new PackageUserState(), UserHandle.getCallingUserId());
+        throw new UnsupportedOperationException(
+                "getPackageArchiveInfo() not implemented in subclass");
     }
 
     /**
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index b054304..8fbf287 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -34,7 +34,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StyleableRes;
-import android.app.ActivityThread;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -222,13 +221,15 @@
     private static final int MAX_FILE_NAME_SIZE = 223;
 
     /**
-     * @see #parseDefault(ParseInput, File, int, boolean)
+     * @see #parseDefault(ParseInput, File, int, List, boolean)
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
-            @ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            boolean collectCertificates) {
         ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
-        return parseDefault(input, file, parseFlags, collectCertificates);
+        return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates);
     }
 
     /**
@@ -238,28 +239,32 @@
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
-            @ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            boolean collectCertificates) {
         ParseResult<ParsingPackage> result;
 
-        ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
-            @Override
-            public boolean hasFeature(String feature) {
-                // Assume the device doesn't support anything. This will affect permission parsing
-                // and will force <uses-permission/> declarations to include all requiredNotFeature
-                // permissions and exclude all requiredFeature permissions. This mirrors the old
-                // behavior.
-                return false;
-            }
+        ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
+                new Callback() {
+                    @Override
+                    public boolean hasFeature(String feature) {
+                        // Assume the device doesn't support anything. This will affect permission
+                        // parsing and will force <uses-permission/> declarations to include all
+                        // requiredNotFeature permissions and exclude all requiredFeature
+                        // permissions. This mirrors the old behavior.
+                        return false;
+                    }
 
-            @Override
-            public ParsingPackage startParsingPackage(
-                    @NonNull String packageName,
-                    @NonNull String baseApkPath,
-                    @NonNull String path,
-                    @NonNull TypedArray manifestArray, boolean isCoreApp) {
-                return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
-            }
-        });
+                    @Override
+                    public ParsingPackage startParsingPackage(
+                            @NonNull String packageName,
+                            @NonNull String baseApkPath,
+                            @NonNull String path,
+                            @NonNull TypedArray manifestArray, boolean isCoreApp) {
+                        return new ParsingPackageImpl(packageName, baseApkPath, path,
+                                manifestArray);
+                    }
+                });
         try {
             result = parser.parsePackage(input, file, parseFlags);
             if (result.isError()) {
@@ -290,13 +295,18 @@
     private boolean mOnlyCoreApps;
     private String[] mSeparateProcesses;
     private DisplayMetrics mDisplayMetrics;
+    @NonNull
+    private List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
     private Callback mCallback;
 
     public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
-            DisplayMetrics displayMetrics, @NonNull Callback callback) {
+            DisplayMetrics displayMetrics,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            @NonNull Callback callback) {
         mOnlyCoreApps = onlyCoreApps;
         mSeparateProcesses = separateProcesses;
         mDisplayMetrics = displayMetrics;
+        mSplitPermissionInfos = splitPermissions;
         mCallback = callback;
     }
 
@@ -2743,14 +2753,10 @@
         }
     }
 
-    private static void convertSplitPermissions(ParsingPackage pkg) {
-        final List<PermissionManager.SplitPermissionInfo> splitPermissions =
-                ActivityThread.currentApplication().getSystemService(PermissionManager.class)
-                        .getSplitPermissions();
-
-        final int listSize = splitPermissions.size();
+    private void convertSplitPermissions(ParsingPackage pkg) {
+        final int listSize = mSplitPermissionInfos.size();
         for (int is = 0; is < listSize; is++) {
-            final PermissionManager.SplitPermissionInfo spi = splitPermissions.get(is);
+            final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
             List<String> requestedPermissions = pkg.getRequestedPermissions();
             if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
                     || !requestedPermissions.contains(spi.getSplitPermission())) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 95f1d12..48b05b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -47,20 +47,22 @@
      * begins adjusting the power state to match what was requested.
      * </p>
      *
+     * @param groupId The identifier for the display group being requested to change power state
      * @param request The requested power state.
-     * @param waitForNegativeProximity If true, issues a request to wait for
+     * @param waitForNegativeProximity If {@code true}, issues a request to wait for
      * negative proximity before turning the screen back on, assuming the screen
      * was turned off by the proximity sensor.
-     * @return True if display is ready, false if there are important changes that must
-     * be made asynchronously (such as turning the screen on), in which case the caller
-     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
-     * then try the request again later until the state converges.
+     * @return {@code true} if display group is ready, {@code false} if there are important
+     * changes that must be made asynchronously (such as turning the screen on), in which case
+     * the caller should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged}
+     * then try the request again later until the state converges. If the provided {@code groupId}
+     * cannot be found then {@code true} will be returned.
      */
-    public abstract boolean requestPowerState(DisplayPowerRequest request,
+    public abstract boolean requestPowerState(int groupId, DisplayPowerRequest request,
             boolean waitForNegativeProximity);
 
     /**
-     * Returns true if the proximity sensor screen-off function is available.
+     * Returns {@code true} if the proximity sensor screen-off function is available.
      */
     public abstract boolean isProximitySensorAvailable();
 
@@ -71,6 +73,22 @@
     public abstract int getDisplayGroupId(int displayId);
 
     /**
+     * Registers a display group listener which will be informed of the addition, removal, or change
+     * of display groups.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerDisplayGroupListener(DisplayGroupListener listener);
+
+    /**
+     * Unregisters a display group listener which will be informed of the addition, removal, or
+     * change of display groups.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener);
+
+    /**
      * Screenshot for internal system-only use such as rotation, etc.  This method includes
      * secure layers and the result should never be exposed to non-system applications.
      * This method does not apply any rotation and provides the output in natural orientation.
diff --git a/core/java/android/net/UnderlyingNetworkInfo.java b/core/java/android/net/UnderlyingNetworkInfo.java
index 8fb4832..7bf9231 100644
--- a/core/java/android/net/UnderlyingNetworkInfo.java
+++ b/core/java/android/net/UnderlyingNetworkInfo.java
@@ -16,11 +16,15 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -30,6 +34,7 @@
  *
  * @hide
  */
+@SystemApi(client = MODULE_LIBRARIES)
 public final class UnderlyingNetworkInfo implements Parcelable {
     /** The owner of this network. */
     public final int ownerUid;
@@ -46,7 +51,7 @@
         Objects.requireNonNull(underlyingIfaces);
         this.ownerUid = ownerUid;
         this.iface = iface;
-        this.underlyingIfaces = underlyingIfaces;
+        this.underlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
     }
 
     private UnderlyingNetworkInfo(@NonNull Parcel in) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b229212..790773f 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -65,7 +65,7 @@
         DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
 
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
-        DEFAULT_FLAGS.put("settings_silky_home", "false");
+        DEFAULT_FLAGS.put("settings_silky_home", "true");
         DEFAULT_FLAGS.put("settings_contextual_home", "false");
         DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9a412fc..749c0df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9806,20 +9806,6 @@
         }
     }
 
-    private void notifyAttachForDrawables() {
-        if (mBackground != null) mBackground.onAttached(this);
-        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
-            mForegroundInfo.mDrawable.onAttached(this);
-        }
-    }
-
-    private void notifyDetachForDrawables() {
-        if (mBackground != null) mBackground.onDetached(this);
-        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
-            mForegroundInfo.mDrawable.onDetached(this);
-        }
-    }
-
     private void setNotifiedContentCaptureAppeared() {
         mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
         mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -10367,6 +10353,7 @@
                         for (int i = 0; i < childDrawIndex; i++) {
                             drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
                         }
+                        preorderedList.clear();
                     } else {
                         final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
                         final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
@@ -20671,7 +20658,6 @@
 
         notifyEnterOrExitForAutoFillIfNeeded(true);
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
-        notifyAttachForDrawables();
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -20721,7 +20707,6 @@
 
         notifyEnterOrExitForAutoFillIfNeeded(false);
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
-        notifyDetachForDrawables();
     }
 
     /**
@@ -23859,7 +23844,6 @@
         if (mBackground != null) {
             if (isAttachedToWindow()) {
                 mBackground.setVisible(false, false);
-                mBackground.onDetached(this);
             }
             mBackground.setCallback(null);
             unscheduleDrawable(mBackground);
@@ -23909,7 +23893,6 @@
                 background.setState(getDrawableState());
             }
             if (isAttachedToWindow()) {
-                background.onAttached(this);
                 background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
             }
 
@@ -24142,7 +24125,6 @@
         if (mForegroundInfo.mDrawable != null) {
             if (isAttachedToWindow()) {
                 mForegroundInfo.mDrawable.setVisible(false, false);
-                mForegroundInfo.mDrawable.onDetached(this);
             }
             mForegroundInfo.mDrawable.setCallback(null);
             unscheduleDrawable(mForegroundInfo.mDrawable);
@@ -24160,7 +24142,6 @@
             }
             applyForegroundTint();
             if (isAttachedToWindow()) {
-                foreground.onAttached(this);
                 foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
             }
             // Set callback last, since the view may still be initializing.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 844fc26..5e3599d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -115,7 +115,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.RenderNode;
-import android.graphics.drawable.BackgroundBlurDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.hardware.display.DisplayManager;
@@ -187,6 +186,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
@@ -672,14 +672,6 @@
             new BackgroundBlurDrawable.Aggregator(this);
 
     /**
-     * @return {@link BackgroundBlurDrawable.Aggregator} for this instance.
-     */
-    @NonNull
-    public BackgroundBlurDrawable.Aggregator getBlurRegionAggregator() {
-        return mBlurRegionAggregator;
-    }
-
-    /**
      * @return {@link ImeFocusController} for this instance.
      */
     @NonNull
@@ -10084,6 +10076,13 @@
         }
     }
 
+    /**
+     * Creates a background blur drawable for the backing {@link Surface}.
+     */
+    public BackgroundBlurDrawable createBackgroundBlurDrawable() {
+        return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
+    }
+
     @Override
     public void onDescendantUnbufferedRequested() {
         mUnbufferedInputSource = mView.mUnbufferedInputSource;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 8319b74..63f1eed 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1511,13 +1511,9 @@
          *  Use {@link #dimAmount} to control the amount of dim. */
         public static final int FLAG_DIM_BEHIND        = 0x00000002;
 
-        /** Window flag: enable blurring behind this window.
-         * To set the amount of blur, use {@link #backgroundBlurRadius}
-         *
-         * @hide
-         */
-        @RequiresPermission(permission.USE_BACKGROUND_BLUR)
-        @SystemApi
+        /** Window flag: blur everything behind this window.
+         * @deprecated Blurring is no longer supported. */
+        @Deprecated
         public static final int FLAG_BLUR_BEHIND        = 0x00000004;
 
         /** Window flag: this window won't ever get key input focus, so the
@@ -3237,14 +3233,10 @@
         public boolean preferMinimalPostProcessing = false;
 
         /**
-         * When {@link FLAG_BLUR_BEHIND} is set, this is the amount of blur in pixels that this
-         * window will use to blur behind itself.
-         * The range is from 0, which means no blur, to 150.
+         * Indicates that this window wants to have blurred content behind it.
          *
          * @hide
          */
-        @SystemApi
-        @RequiresPermission(permission.USE_BACKGROUND_BLUR)
         public int backgroundBlurRadius = 0;
 
         /**
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 023d9ff2..20230e7 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -98,9 +98,17 @@
     public abstract boolean acceptThirdPartyCookies(WebView webview);
 
     /**
-     * Sets a cookie for the given URL. Any existing cookie with the same host,
-     * path and name will be replaced with the new cookie. The cookie being set
-     * will be ignored if it is expired.
+     * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+     * host, path and name will be replaced with the new cookie. The cookie being set
+     * will be ignored if it is expired. To set multiple cookies, your application should invoke
+     * this method multiple times.
+     *
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+     * response header defined by
+     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+     * consult the RFC specification for a list of valid attributes.
      *
      * <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
      * attribute, {@code url} must use the {@code "https://"} scheme.
@@ -112,13 +120,20 @@
     public abstract void setCookie(String url, String value);
 
     /**
-     * Sets a cookie for the given URL. Any existing cookie with the same host,
-     * path and name will be replaced with the new cookie. The cookie being set
-     * will be ignored if it is expired.
-     * <p>
-     * This method is asynchronous.
-     * If a {@link ValueCallback} is provided,
-     * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+     * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+     * host, path and name will be replaced with the new cookie. The cookie being set
+     * will be ignored if it is expired. To set multiple cookies, your application should invoke
+     * this method multiple times.
+     *
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+     * response header defined by
+     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+     * consult the RFC specification for a list of valid attributes.
+     *
+     * <p>This method is asynchronous. If a {@link ValueCallback} is provided,
+     * {@link ValueCallback#onReceiveValue} will be called on the current
      * thread's {@link android.os.Looper} once the operation is complete.
      * The value provided to the callback indicates whether the cookie was set successfully.
      * You can pass {@code null} as the callback if you don't need to know when the operation
@@ -137,7 +152,10 @@
             callback);
 
     /**
-     * Gets the cookies for the given URL.
+     * Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
+     * cookies are associated with this URL, in which case each cookie will be delimited by {@code
+     * "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
+     * {@code "key=value"}.
      *
      * @param url the URL for which the cookies are requested
      * @return value the cookies as a string, using the format of the 'Cookie'
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index af666d8..9a44c05 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.content;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -234,7 +235,7 @@
     /**
      * Called when an existing package is updated or its disabled state changes.
      */
-    public void onPackageModified(String packageName) {
+    public void onPackageModified(@NonNull String packageName) {
     }
     
     public boolean didSomePackagesChange() {
diff --git a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
similarity index 78%
rename from graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
rename to core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 7e75c5b..96dac56 100644
--- a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package android.graphics.drawable;
+package com.android.internal.graphics.drawable;
 
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -31,71 +30,59 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.SurfaceControl;
-import android.view.View;
 import android.view.ViewRootImpl;
 
+import com.android.internal.R;
+
 /**
  * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
  * to SurfaceFlinger.
- *
- * @hide
  */
-@SystemApi
 public final class BackgroundBlurDrawable extends Drawable {
+
     private static final String TAG = BackgroundBlurDrawable.class.getSimpleName();
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private final Aggregator mAggregator;
     private final RenderNode mRenderNode;
     private final Paint mPaint = new Paint();
     private final Path mRectPath = new Path();
     private final float[] mTmpRadii = new float[8];
     private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();
 
-    private Aggregator mAggregator;
-
     // This will be called from a thread pool.
     private final RenderNode.PositionUpdateListener mPositionUpdateListener =
             new RenderNode.PositionUpdateListener() {
             @Override
             public void positionChanged(long frameNumber, int left, int top, int right,
                     int bottom) {
-                if (mAggregator == null) {
+                synchronized (mAggregator) {
                     mBlurRegion.rect.set(left, top, right, bottom);
-                } else {
-                    synchronized (mAggregator) {
-                        mBlurRegion.rect.set(left, top, right, bottom);
-                        mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
-                    }
+                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                 }
             }
 
             @Override
             public void positionLost(long frameNumber) {
-                if (mAggregator == null) {
+                synchronized (mAggregator) {
                     mBlurRegion.rect.setEmpty();
-                } else {
-                    synchronized (mAggregator) {
-                        mBlurRegion.rect.setEmpty();
-                        mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
-                    }
+                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                 }
             }
         };
 
-    @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR)
-    public BackgroundBlurDrawable() {
+    private BackgroundBlurDrawable(Aggregator aggregator) {
+        mAggregator = aggregator;
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
         mPaint.setColor(Color.TRANSPARENT);
         mRenderNode = new RenderNode("BackgroundBlurDrawable");
         mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void draw(@NonNull Canvas canvas) {
         if (mRectPath.isEmpty() || !isVisible() || getAlpha() == 0) {
@@ -113,9 +100,6 @@
         mPaint.setColor(color);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
         boolean changed = super.setVisible(visible, restart);
@@ -125,9 +109,6 @@
         return changed;
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void setAlpha(int alpha) {
         mBlurRegion.alpha = alpha / 255f;
@@ -158,12 +139,12 @@
      */
     public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
             float cornerRadiusBR) {
-        maybeRunSynchronized(() -> {
+        synchronized (mAggregator) {
             mBlurRegion.cornerRadiusTL = cornerRadiusTL;
             mBlurRegion.cornerRadiusTR = cornerRadiusTR;
             mBlurRegion.cornerRadiusBL = cornerRadiusBL;
             mBlurRegion.cornerRadiusBR = cornerRadiusBR;
-        });
+        }
         updatePath();
         invalidateSelf();
     }
@@ -176,13 +157,12 @@
     }
 
     private void updatePath() {
-        maybeRunSynchronized(() -> {
+        synchronized (mAggregator) {
             mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
             mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
             mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
             mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
-        });
-
+        }
         mRectPath.reset();
         if (getAlpha() == 0 || !isVisible()) {
             return;
@@ -192,62 +172,19 @@
                 Path.Direction.CW);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
         throw new IllegalArgumentException("not implemented");
     }
 
-    /**
-     * @hide
-     */
     @Override
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
 
     /**
-     *  @hide
-     */
-    @Override
-    public void onAttached(@NonNull View v) {
-        super.onAttached(v);
-        mAggregator = v.getViewRootImpl().getBlurRegionAggregator();
-    }
-
-    /**
-     *  @hide
-     */
-    @Override
-    public void onDetached(@NonNull View v) {
-        super.onDetached(v);
-        mAggregator = null;
-    }
-
-    /**
-     * The Aggregator is called from the RenderThread to aggregate all blur regions and send them
-     * to SurfaceFlinger. Since the BackgroundBlurDrawable could be updated at any time from the
-     * main thread, we need to synchronize the two threads. The BackgroundBlurDrawable may be
-     * instantiated before the ViewRootImpl is created, i.e. before the Aggregator is created.
-     * In that case, updates are not synchronized.
-     */
-    private void maybeRunSynchronized(Runnable r) {
-        if (mAggregator == null) {
-            r.run();
-        } else {
-            synchronized (mAggregator) {
-                r.run();
-            }
-        }
-    }
-
-    /**
      * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
      * message when it's time to propagate them.
-     *
-     * @hide
      */
     public static final class Aggregator {
 
@@ -262,6 +199,16 @@
         }
 
         /**
+         * Creates a blur region with default radius.
+         */
+        public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
+            BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
+            drawable.setBlurRadius(context.getResources().getDimensionPixelSize(
+                    R.dimen.default_background_blur_radius));
+            return drawable;
+        }
+
+        /**
          * Called from RenderThread only, already locked.
          * @param drawable
          * @param blurRegion
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 3be841c..141dc79 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2540,14 +2540,8 @@
             }
         }
 
-        if (a.getBoolean(R.styleable.Window_windowBackgroundBlurEnabled, false)) {
-            if ((getForcedWindowFlags() & WindowManager.LayoutParams.FLAG_BLUR_BEHIND) == 0) {
-                params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
-            }
-
-            params.backgroundBlurRadius = a.getDimensionPixelSize(
-                        android.R.styleable.Window_windowBackgroundBlurRadius, 0);
-        }
+        params.backgroundBlurRadius = a.getDimensionPixelSize(
+                R.styleable.Window_windowBackgroundBlurRadius, 0);
 
         if (params.windowAnimations == 0) {
             params.windowAnimations = a.getResourceId(
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7c1e1b5f..989e25d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4133,11 +4133,6 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to use background blur.
-         @hide -->
-    <permission android:name="android.permission.USE_BACKGROUND_BLUR"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows an application to control the lights on the device.
          @hide
          @SystemApi
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 07c3adf..26fb8b8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -87,16 +87,6 @@
              theme does not set this value, meaning it is based on whether the
              window is floating. -->
         <attr name="backgroundDimEnabled" format="boolean" />
-        <!-- When windowBackgroundBlurEnabled is set, this is the amount of blur to apply
-             behind the window. The range is from 0, which means no blur, to 150.
-             @hide @SystemApi -->
-        <attr name="windowBackgroundBlurRadius" format="dimension"/>
-        <!-- If set, the area behind the window will be blurred with radius
-             windowBackgroundBlurRadius.
-             @hide @SystemApi -->
-        <attr name="windowBackgroundBlurEnabled" format="boolean" />
-
-
         <!-- Color of background imagery used for popup windows. -->
         <attr name="colorPopupBackground" format="color" />
         <!-- Color used for list divider. -->
@@ -1974,8 +1964,6 @@
         <attr name="textColor" />
         <attr name="backgroundDimEnabled" />
         <attr name="backgroundDimAmount" />
-        <attr name="windowBackgroundBlurEnabled" />
-        <attr name="windowBackgroundBlurRadius" />
         <attr name="windowActionBar" />
         <attr name="windowActionModeOverlay" />
         <attr name="windowActionBarOverlay" />
@@ -2193,6 +2181,10 @@
              the decor view. -->
         <attr name="windowLightNavigationBar" format="boolean" />
 
+        <!-- @hide -->
+        <attr name="windowBackgroundBlurRadius" format="dimension"/>
+
+
         <!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
         <p>
         Defaults to {@code default}.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b76ab3a..2381fc6 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3049,10 +3049,7 @@
     <public name="windowLayoutAffinity" />
     <public name="canPauseRecording" />
     <!-- @hide -->
-    <!-- @hide @SystemApi -->
     <public name="windowBackgroundBlurRadius"/>
-    <!-- @hide @SystemApi -->
-    <public name="windowBackgroundBlurEnabled"/>
     <public name="requireDeviceScreenOn" />
     <public name="pathSuffix" />
     <public name="sspSuffix" />
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
new file mode 100644
index 0000000..af77b6c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public class GenericDocumentTest {
+    @Test
+    public void testRecreateFromParcel() {
+        GenericDocument inDoc =
+                new GenericDocument.Builder<>("uri1", "schema1")
+                        .setScore(42)
+                        .setPropertyString("propString", "Hello")
+                        .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+                        .setPropertyDocument(
+                                "propDocument",
+                                new GenericDocument.Builder<>("uri2", "schema2")
+                                        .setPropertyString("propString", "Goodbye")
+                                        .setPropertyBytes("propBytes", new byte[][] {{3, 4}})
+                                        .build())
+                        .build();
+
+        // Serialize the document
+        Parcel inParcel = Parcel.obtain();
+        inParcel.writeBundle(inDoc.getBundle());
+        byte[] data = inParcel.marshall();
+        inParcel.recycle();
+
+        // Deserialize the document
+        Parcel outParcel = Parcel.obtain();
+        outParcel.unmarshall(data, 0, data.length);
+        outParcel.setDataPosition(0);
+        Bundle outBundle = outParcel.readBundle();
+        outParcel.recycle();
+
+        // Compare results
+        GenericDocument outDoc = new GenericDocument(outBundle);
+        assertThat(inDoc).isEqualTo(outDoc);
+        assertThat(outDoc.getPropertyString("propString")).isEqualTo("Hello");
+        assertThat(outDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+                .isEqualTo("Goodbye");
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+                .isEqualTo(new byte[][] {{3, 4}});
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
index 986079f..7d175d9 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
@@ -34,9 +34,9 @@
                         new AppSearchEmail.Builder("test1").build(),
                         new AppSearchEmail.Builder("test2").build());
         PutDocumentsRequest request =
-                new PutDocumentsRequest.Builder().addGenericDocument(emails).build();
+                new PutDocumentsRequest.Builder().addGenericDocuments(emails).build();
 
-        assertThat(request.getDocuments().get(0).getUri()).isEqualTo("test1");
-        assertThat(request.getDocuments().get(1).getUri()).isEqualTo("test2");
+        assertThat(request.getGenericDocuments().get(0).getUri()).isEqualTo("test1");
+        assertThat(request.getGenericDocuments().get(1).getUri()).isEqualTo("test2");
     }
 }
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
index c8cee85..0fe44630 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
@@ -33,8 +33,8 @@
         SearchSpec searchSpec =
                 new SearchSpec.Builder()
                         .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
-                        .addNamespace("namespace1", "namespace2")
-                        .addSchemaType("schemaTypes1", "schemaTypes2")
+                        .addFilterNamespaces("namespace1", "namespace2")
+                        .addFilterSchemas("schemaTypes1", "schemaTypes2")
                         .addFilterPackageNames("package1", "package2")
                         .setSnippetCount(5)
                         .setSnippetCountPerProperty(10)
@@ -49,7 +49,7 @@
                 .isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
         assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
                 .containsExactly("namespace1", "namespace2");
-        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
+        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_FIELD))
                 .containsExactly("schemaTypes1", "schemaTypes2");
         assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD))
                 .containsExactly("package1", "package2");
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
index aab9229..bf6b07f 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
@@ -75,12 +75,12 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
 
         // By default, the schema is visible.
-        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
 
         request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForSystemUi("Schema", true)
                         .build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
@@ -91,7 +91,7 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForSystemUi("Schema", false)
                         .build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema");
@@ -102,7 +102,7 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
 
         // By default, the schema is not visible.
-        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
         assertThat(request.getSchemasVisibleToPackages()).isEmpty();
 
         PackageIdentifier packageIdentifier =
@@ -112,7 +112,7 @@
 
         request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema", /*visible=*/ true, packageIdentifier)
                         .build();
@@ -126,7 +126,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema",
                                 /*visible=*/ false,
@@ -147,7 +147,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         // Set it visible for "Schema"
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema", /*visible=*/ true, packageIdentifier)
@@ -165,7 +165,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         // First set it as visible
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema",
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 88faa0a..57c0be5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -78,6 +78,7 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -334,7 +335,8 @@
     private ParsingPackage parsePackage(Uri packageURI) {
         final String archiveFilePath = packageURI.getPath();
         ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(
-                new File(archiveFilePath), 0 /*flags*/, false /*collectCertificates*/);
+                new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
+                false /*collectCertificates*/);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 7f22dc2..28b3b04 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1465,24 +1465,6 @@
     }
 
     /**
-     * Notifies the drawable that it has been attached.
-     *
-     * @param v The view that it is attached to
-     * @hide
-     */
-    public void onAttached(View v) {
-    }
-
-    /**
-     * Notifies the drawable that it has been detached.
-     *
-     * @param v The view that it is detached from
-     * @hide
-     */
-    public void onDetached(View v) {
-    }
-
-    /**
      * Sets the source override density for this Drawable. If non-zero, this density is to be used
      * for any calls to {@link Resources#getDrawableForDensity(int, int, Theme)} or
      * {@link Resources#getValueForDensity(int, int, TypedValue, boolean)}.
diff --git a/packages/SystemUI/res/drawable/btn_restart.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/btn_restart.xml
rename to libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index b581f55..93a6e7b 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -97,4 +97,14 @@
         android:padding="@dimen/pip_resize_handle_padding"
         android:src="@drawable/pip_resize_handle"
         android:background="?android:selectableItemBackgroundBorderless" />
-</FrameLayout>
+
+    <!-- invisible layer to trap the focus, b/169372603 -->
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@null"
+        android:defaultFocusHighlightEnabled="false"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:focusedByDefault="true" />
+t </FrameLayout>
diff --git a/packages/SystemUI/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
similarity index 100%
rename from packages/SystemUI/res/layout/size_compat_mode_hint.xml
rename to libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 30ef72c..b1425e4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,4 +146,10 @@
 
     <!-- Content description to tell the user a bubble has been dismissed. -->
     <string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
+
+    <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
+    <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+    <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
+    <string name="got_it">Got it</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 4b3fc81..afe523a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -106,5 +106,4 @@
     public String toString() {
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
-
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index c9b38d0..a570c0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -102,18 +103,31 @@
     private final Object mLock = new Object();
     private final StartingSurfaceDrawer mStartingSurfaceDrawer;
 
+    /**
+     * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
+     * compat.
+     */
+    @Nullable
+    private final SizeCompatUI mSizeCompatUI;
+
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null, mainExecutor, context);
+        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+    }
+
+    public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
+            SizeCompatUI sizeCompatUI) {
+        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context) {
+            Context context, @Nullable SizeCompatUI sizeCompatUI) {
         super(taskOrganizerController, mainExecutor);
         // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
         //  by a controller, that class should be create while porting
         //  ActivityRecord#addStartingWindow to WMShell.
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
+        mSizeCompatUI = sizeCompatUI;
     }
 
     @Override
@@ -255,6 +269,7 @@
         if (listener != null) {
             listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
         }
+        notifySizeCompatUI(info.getTaskInfo(), listener);
     }
 
     @Override
@@ -270,6 +285,10 @@
             if (!updated && newListener != null) {
                 newListener.onTaskInfoChanged(taskInfo);
             }
+            if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+                // Notify the size compat UI if the listener or task info changed.
+                notifySizeCompatUI(taskInfo, newListener);
+            }
         }
     }
 
@@ -294,6 +313,8 @@
             if (listener != null) {
                 listener.onTaskVanished(taskInfo);
             }
+            // Pass null for listener to remove the size compat UI on this task if there is any.
+            notifySizeCompatUI(taskInfo, null /* taskListener */);
         }
     }
 
@@ -320,6 +341,34 @@
         return true;
     }
 
+    /**
+     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
+     * the UI accordingly.
+     *
+     * @param taskInfo the new Task info
+     * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
+     *                     vanished.
+     */
+    private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
+        if (mSizeCompatUI == null) {
+            return;
+        }
+
+        // The task is vanished, notify to remove size compat UI on this Task if there is any.
+        if (taskListener == null) {
+            mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+                    null /* taskConfig */, null /* sizeCompatActivity*/,
+                    null /* taskListener */);
+            return;
+        }
+
+        mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+                taskInfo.configuration.windowConfiguration.getBounds(),
+                // null if the top activity not in size compat.
+                taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
+                taskListener);
+    }
+
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
         return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index a2cd683..b2ac61c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -159,6 +159,14 @@
         }
     }
 
+    private void dispatchVisibilityChanged(int displayId, boolean isShowing) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeVisibilityChanged(displayId, isShowing);
+            }
+        }
+    }
+
     /**
      * Adds an {@link ImePositionProcessor} to be called during ime position updates.
      */
@@ -212,7 +220,7 @@
                 return;
             }
 
-            mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+            updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
 
             final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
             final Rect newFrame = newSource.getFrame();
@@ -371,7 +379,7 @@
                 seek = true;
             }
             mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
-            mImeShowing = show;
+            updateImeVisibility(show);
             mAnimation = ValueAnimator.ofFloat(startY, endY);
             mAnimation.setDuration(
                     show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
@@ -455,6 +463,13 @@
             }
         }
 
+        private void updateImeVisibility(boolean isShowing) {
+            if (mImeShowing != isShowing) {
+                mImeShowing = isShowing;
+                dispatchVisibilityChanged(mDisplayId, isShowing);
+            }
+        }
+
         @VisibleForTesting
         @BinderThread
         public class DisplayWindowInsetsControllerImpl
@@ -563,6 +578,15 @@
         default void onImeEndPositioning(int displayId, boolean cancel,
                 SurfaceControl.Transaction t) {
         }
+
+        /**
+         * Called when the IME visibility changed.
+         *
+         * @param isShowing {@code true} if the IME is shown.
+         */
+        default void onImeVisibilityChanged(int displayId, boolean isShowing) {
+
+        }
     }
 
     public IInputMethodManager getImms() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
new file mode 100644
index 0000000..e47e1ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import android.app.ActivityClient;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import com.android.wm.shell.R;
+
+/** Button to restart the size compat activity. */
+class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+        View.OnLongClickListener {
+    private static final String TAG = "SizeCompatRestartButton";
+
+    final WindowManager.LayoutParams mWinParams;
+    final boolean mShouldShowHint;
+    final int mDisplayId;
+    final int mPopupOffsetX;
+    final int mPopupOffsetY;
+
+    private IBinder mLastActivityToken;
+    private PopupWindow mShowingHint;
+
+    SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+        super(context);
+        mDisplayId = displayId;
+        mShouldShowHint = !hasShownHint;
+        final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
+        setImageDrawable(drawable);
+        setContentDescription(context.getString(R.string.restart_button_description));
+
+        final int drawableW = drawable.getIntrinsicWidth();
+        final int drawableH = drawable.getIntrinsicHeight();
+        mPopupOffsetX = drawableW / 2;
+        mPopupOffsetY = drawableH * 2;
+
+        final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
+        final GradientDrawable mask = new GradientDrawable();
+        mask.setShape(GradientDrawable.OVAL);
+        mask.setColor(color);
+        setBackground(new RippleDrawable(color, null /* content */, mask));
+        setOnClickListener(this);
+        setOnLongClickListener(this);
+
+        mWinParams = new WindowManager.LayoutParams();
+        mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
+        mWinParams.width = drawableW * 2;
+        mWinParams.height = drawableH * 2;
+        mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        mWinParams.format = PixelFormat.TRANSLUCENT;
+        mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
+                + context.getDisplayId());
+    }
+
+    void updateLastTargetActivity(IBinder activityToken) {
+        mLastActivityToken = activityToken;
+    }
+
+    /** @return {@code false} if the target display is invalid. */
+    boolean show() {
+        try {
+            getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
+        } catch (WindowManager.InvalidDisplayException e) {
+            // The target display may have been removed when the callback has just arrived.
+            Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
+            return false;
+        }
+        return true;
+    }
+
+    void remove() {
+        if (mShowingHint != null) {
+            mShowingHint.dismiss();
+        }
+        getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        showHint();
+        return true;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mShouldShowHint) {
+            showHint();
+        }
+    }
+
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        final int gravity = getGravity(layoutDirection);
+        if (mWinParams.gravity != gravity) {
+            mWinParams.gravity = gravity;
+            if (mShowingHint != null) {
+                mShowingHint.dismiss();
+                showHint();
+            }
+            getContext().getSystemService(WindowManager.class).updateViewLayout(this,
+                    mWinParams);
+        }
+        super.setLayoutDirection(layoutDirection);
+    }
+
+    void showHint() {
+        if (mShowingHint != null) {
+            return;
+        }
+
+        final View popupView = LayoutInflater.from(getContext()).inflate(
+                R.layout.size_compat_mode_hint, null /* root */);
+        final PopupWindow popupWindow = new PopupWindow(popupView,
+                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        popupWindow.setWindowLayoutType(mWinParams.type);
+        popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
+        popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
+        popupWindow.setClippingEnabled(false);
+        popupWindow.setOnDismissListener(() -> mShowingHint = null);
+        mShowingHint = popupWindow;
+
+        final Button gotItButton = popupView.findViewById(R.id.got_it);
+        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
+                null /* content */, null /* mask */));
+        gotItButton.setOnClickListener(view -> popupWindow.dismiss());
+        popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+    }
+
+    private static int getGravity(int layoutDirection) {
+        return Gravity.BOTTOM
+                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
new file mode 100644
index 0000000..11f22ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface to engage size compat mode UI.
+ */
+@ExternalThread
+public interface SizeCompatUI {
+    /**
+     * Called when the Task info changed. Creates and updates the restart button if there is an
+     * activity in size compat, or removes the restart button if there is no size compat activity.
+     *
+     * @param displayId display the task and activity are in.
+     * @param taskId task the activity is in.
+     * @param taskBounds task bounds to place the restart button in.
+     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
+     *                           top activity in this Task is not in size compat.
+     * @param taskListener listener to handle the Task Surface placement.
+     */
+    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+            @Nullable IBinder sizeCompatActivity,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
new file mode 100644
index 0000000..8cb16c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Shows a restart-activity button on Task when the foreground activity is in size compatibility
+ * mode.
+ */
+public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
+        DisplayImeController.ImePositionProcessor {
+    private static final String TAG = "SizeCompatUI";
+
+    /** The showing buttons by task id. */
+    private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+    /** Avoid creating display context frequently for non-default display. */
+    private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
+
+    @VisibleForTesting
+    final SizeCompatUI mImpl = new SizeCompatUIImpl();
+    private final Context mContext;
+    private final ShellExecutor mMainExecutor;
+    private final DisplayController mDisplayController;
+    private final DisplayImeController mImeController;
+
+    /** Only show once automatically in the process life. */
+    private boolean mHasShownHint;
+
+    /** Creates the {@link SizeCompatUIController}. */
+    public static SizeCompatUI create(Context context,
+            DisplayController displayController,
+            DisplayImeController imeController,
+            ShellExecutor mainExecutor) {
+        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
+                .mImpl;
+    }
+
+    @VisibleForTesting
+    SizeCompatUIController(Context context,
+            DisplayController displayController,
+            DisplayImeController imeController,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mMainExecutor = mainExecutor;
+        mDisplayController = displayController;
+        mImeController = imeController;
+        mDisplayController.addDisplayWindowListener(this);
+        mImeController.addPositionProcessor(this);
+    }
+
+    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+            @Nullable IBinder sizeCompatActivity,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+        // TODO Draw button on Task surface
+        if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+            // Null token means the current foreground activity is not in size compatibility mode.
+            removeRestartButton(taskId);
+        } else {
+            updateRestartButton(displayId, taskId, sizeCompatActivity);
+        }
+    }
+
+    // TODO move from SizeCompatModeActivityController from system UI.
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        mDisplayContextCache.remove(displayId);
+        for (int i = 0; i < mActiveButtons.size(); i++) {
+            final int taskId = mActiveButtons.keyAt(i);
+            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+            if (button != null && button.mDisplayId == displayId) {
+                removeRestartButton(taskId);
+            }
+        }
+    }
+
+    @Override
+    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+        final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
+        for (int i = 0; i < mActiveButtons.size(); i++) {
+            final int taskId = mActiveButtons.keyAt(i);
+            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+            if (button == null || button.mDisplayId != displayId) {
+                continue;
+            }
+
+            // Hide the button when input method is showing.
+            if (button.getVisibility() != newVisibility) {
+                button.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
+        SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
+        if (restartButton != null) {
+            restartButton.updateLastTargetActivity(activityToken);
+            return;
+        }
+
+        final Context context = getOrCreateDisplayContext(displayId);
+        if (context == null) {
+            Log.i(TAG, "Cannot get context for display " + displayId);
+            return;
+        }
+
+        restartButton = createRestartButton(context, displayId);
+        restartButton.updateLastTargetActivity(activityToken);
+        if (restartButton.show()) {
+            mActiveButtons.append(taskId, restartButton);
+        } else {
+            onDisplayRemoved(displayId);
+        }
+    }
+
+    @VisibleForTesting
+    SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+        final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+                mHasShownHint);
+        // Only show hint for the first time.
+        mHasShownHint = true;
+        return button;
+    }
+
+    private void removeRestartButton(int taskId) {
+        final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+        if (button != null) {
+            button.remove();
+            mActiveButtons.remove(taskId);
+        }
+    }
+
+    private Context getOrCreateDisplayContext(int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            return mContext;
+        }
+        Context context = null;
+        final WeakReference<Context> ref = mDisplayContextCache.get(displayId);
+        if (ref != null) {
+            context = ref.get();
+        }
+        if (context == null) {
+            Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
+            if (display != null) {
+                context = mContext.createDisplayContext(display);
+                mDisplayContextCache.put(displayId, new WeakReference<>(context));
+            }
+        }
+        return context;
+    }
+
+    private class SizeCompatUIImpl implements SizeCompatUI {
+        @Override
+        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+                @Nullable IBinder sizeCompatActivity,
+                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+            mMainExecutor.execute(() ->
+                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
+                            taskBounds, sizeCompatActivity, taskListener));
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 862776e..80ea9b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,11 +16,14 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 
@@ -48,6 +51,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,6 +63,9 @@
 
 /**
  * Tests for the shell task organizer.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:ShellTaskOrganizerTests
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -68,6 +75,8 @@
     private ITaskOrganizerController mTaskOrganizerController;
     @Mock
     private Context mContext;
+    @Mock
+    private SizeCompatUI mSizeCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -102,7 +111,8 @@
             doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
-        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext));
+        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
+                mSizeCompatUI));
     }
 
     @Test
@@ -257,6 +267,39 @@
         assertTrue(mwListener.appeared.contains(task2));
     }
 
+    @Test
+    public void testOnSizeCompatActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.topActivityToken = mock(IBinder.class);
+        taskInfo1.topActivityInSizeCompat = false;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // sizeCompatActivity is null if top activity is not in size compat.
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                taskInfo1.configuration.windowConfiguration.getBounds(),
+                null /* sizeCompatActivity*/ , taskListener);
+
+        // sizeCompatActivity is non-null if top activity is in size compat.
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.topActivityToken = taskInfo1.topActivityToken;
+        taskInfo2.topActivityInSizeCompat = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                taskInfo1.configuration.windowConfiguration.getBounds(),
+                taskInfo1.topActivityToken,
+                taskListener);
+
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                null /* taskConfig */, null /* sizeCompatActivity*/,
+                null /* taskListener */);
+    }
+
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
new file mode 100644
index 0000000..98f01ff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUIController}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatUIControllerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUIControllerTest extends ShellTestCase {
+    private static final int DISPLAY_ID = 0;
+
+    private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+
+    private SizeCompatUIController mController;
+    private @Mock DisplayController mMockDisplayController;
+    private @Mock DisplayImeController mMockImeController;
+    private @Mock SizeCompatRestartButton mMockButton;
+    private @Mock IBinder mMockActivityToken;
+    private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(true).when(mMockButton).show();
+
+        mController = new SizeCompatUIController(mContext, mMockDisplayController,
+                mMockImeController, mShellMainExecutor) {
+            @Override
+            SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+                return mMockButton;
+            }
+        };
+    }
+
+    @Test
+    public void testListenerRegistered() {
+        verify(mMockDisplayController).addDisplayWindowListener(mController);
+        verify(mMockImeController).addPositionProcessor(mController);
+    }
+
+    @Test
+    public void testOnSizeCompatInfoChanged() {
+        final int taskId = 12;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+
+        // Verify that the restart button is added with non-null size compat activity.
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+                mMockActivityToken, mMockTaskListener);
+        mShellMainExecutor.flushAll();
+
+        verify(mMockButton).show();
+        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
+
+        // Verify that the restart button is removed with null size compat activity.
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+
+        mShellMainExecutor.flushAll();
+        verify(mMockButton).remove();
+    }
+
+    @Test
+    public void testChangeButtonVisibilityOnImeShowHide() {
+        final int taskId = 12;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+                mMockActivityToken, mMockTaskListener);
+        mShellMainExecutor.flushAll();
+
+        // Verify that the restart button is hidden when IME is visible.
+        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
+        mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+
+        verify(mMockButton).setVisibility(eq(View.GONE));
+
+        // Verify that the restart button is visible when IME is hidden.
+        doReturn(View.GONE).when(mMockButton).getVisibility();
+        mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+
+        verify(mMockButton).setVisibility(eq(View.VISIBLE));
+    }
+}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 65a0110..3dcaffb 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1380,10 +1380,9 @@
      * Gets the Automatic Gain Control level in dB.
      *
      * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
-     * level may be used to indicate potential interference. When AGC is at a nominal level, this
-     * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive
-     * number. Hence in cases of strong jamming, in the band of this signal, this value will go more
-     * negative.
+     * level may be used to indicate potential interference. Higher gain (and/or lower input power)
+     * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
+     * signal, this value will go more negative.
      *
      * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
      * components) may also affect the typical output of of this value on any given hardware design
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 9657b25e..4d7ed11 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1089,7 +1089,7 @@
 
         private int[] mSampleRates;
         private Range<Integer>[] mSampleRateRanges;
-        private int mMaxInputChannelCount;
+        private Range<Integer>[] mInputChannelRanges;
 
         private static final int MAX_INPUT_CHANNEL_COUNT = 30;
 
@@ -1119,11 +1119,61 @@
         }
 
         /**
-         * Returns the maximum number of input channels supported.  The codec
-         * supports any number of channels between 1 and this maximum value.
+         * Returns the maximum number of input channels supported.
+         *
+         * Through {@link android.os.Build.VERSION_CODES#R}, this method indicated support
+         * for any number of input channels between 1 and this maximum value.
+         *
+         * As of {@link android.os.Build.VERSION_CODES#S},
+         * the implied lower limit of 1 channel is no longer valid.
+         * As of {@link android.os.Build.VERSION_CODES#S}, {@link #getMaxInputChannelCount} is
+         * superseded by {@link #getInputChannelCountRanges},
+         * which returns an array of ranges of channels.
+         * The {@link #getMaxInputChannelCount} method will return the highest value
+         * in the ranges returned by {@link #getInputChannelCountRanges}
+         *
          */
         public int getMaxInputChannelCount() {
-            return mMaxInputChannelCount;
+            int overall_max = 0;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmax = mInputChannelRanges[i].getUpper();
+                if (lmax > overall_max) {
+                    overall_max = lmax;
+                }
+            }
+            return overall_max;
+        }
+
+        /**
+         * Returns the minimum number of input channels supported.
+         * This is often 1, but does vary for certain mime types.
+         *
+         * This returns the lowest channel count in the ranges returned by
+         * {@link #getInputChannelCountRanges}.
+         */
+        public int getMinInputChannelCount() {
+            int overall_min = MAX_INPUT_CHANNEL_COUNT;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmin = mInputChannelRanges[i].getLower();
+                if (lmin < overall_min) {
+                    overall_min = lmin;
+                }
+            }
+            return overall_min;
+        }
+
+        /*
+         * Returns an array of ranges representing the number of input channels supported.
+         * The codec supports any number of input channels within this range.
+         *
+         * This supersedes the {@link #getMaxInputChannelCount} method.
+         *
+         * For many codecs, this will be a single range [1..N], for some N.
+         */
+        @SuppressLint("ArrayReturn")
+        @NonNull
+        public Range<Integer>[] getInputChannelCountRanges() {
+            return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
         }
 
         /* no public constructor */
@@ -1146,7 +1196,7 @@
 
         private void initWithPlatformLimits() {
             mBitrateRange = Range.create(0, Integer.MAX_VALUE);
-            mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
+            mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
             // mBitrateRange = Range.create(1, 320000);
             final int minSampleRate = SystemProperties.
                 getInt("ro.mediacodec.min_sample_rate", 7350);
@@ -1158,9 +1208,12 @@
 
         private boolean supports(Integer sampleRate, Integer inputChannels) {
             // channels and sample rates are checked orthogonally
-            if (inputChannels != null &&
-                    (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
-                return false;
+            if (inputChannels != null) {
+                int ix = Utils.binarySearchDistinctRanges(
+                        mInputChannelRanges, inputChannels);
+                if (ix < 0) {
+                    return false;
+                }
             }
             if (sampleRate != null) {
                 int ix = Utils.binarySearchDistinctRanges(
@@ -1292,12 +1345,28 @@
             } else if (sampleRateRange != null) {
                 limitSampleRates(new Range[] { sampleRateRange });
             }
-            applyLimits(maxChannels, bitRates);
+
+            Range<Integer> channelRange = Range.create(1, maxChannels);
+
+            applyLimits(new Range[] { channelRange }, bitRates);
         }
 
-        private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
-            mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
-                    .clamp(maxInputChannels);
+        private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+            // clamp & make a local copy
+            Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+            for (int i = 0; i < inputChannels.length; i++) {
+                int lower = inputChannels[i].clamp(1);
+                int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+                myInputChannels[i] = Range.create(lower, upper);
+            }
+
+            // sort, intersect with existing, & save channel list
+            sortDistinctRanges(myInputChannels);
+            Range<Integer>[] joinedChannelList =
+                            intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+            mInputChannelRanges = joinedChannelList;
+
             if (bitRates != null) {
                 mBitrateRange = mBitrateRange.intersect(bitRates);
             }
@@ -1305,6 +1374,7 @@
 
         private void parseFromInfo(MediaFormat info) {
             int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+            Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
             Range<Integer> bitRates = POSITIVE_INTEGERS;
 
             if (info.containsKey("sample-rate-ranges")) {
@@ -1315,17 +1385,38 @@
                 }
                 limitSampleRates(rateRanges);
             }
-            if (info.containsKey("max-channel-count")) {
+
+            // we will prefer channel-ranges over max-channel-count
+            if (info.containsKey("channel-ranges")) {
+                String[] channelStrings = info.getString("channel-ranges").split(",");
+                Range<Integer>[] channelRanges = new Range[channelStrings.length];
+                for (int i = 0; i < channelStrings.length; i++) {
+                    channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+                }
+                channels = channelRanges;
+            } else if (info.containsKey("channel-range")) {
+                Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+                                                              null);
+                channels = new Range[] { oneRange };
+            } else if (info.containsKey("max-channel-count")) {
                 maxInputChannels = Utils.parseIntSafely(
                         info.getString("max-channel-count"), maxInputChannels);
+                if (maxInputChannels == 0) {
+                    channels = new Range[] {Range.create(0, 0)};
+                } else {
+                    channels = new Range[] {Range.create(1, maxInputChannels)};
+                }
             } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
                 maxInputChannels = 0;
+                channels = new Range[] {Range.create(0, 0)};
             }
+
             if (info.containsKey("bitrate-range")) {
                 bitRates = bitRates.intersect(
                         Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
             }
-            applyLimits(maxInputChannels, bitRates);
+
+            applyLimits(channels, bitRates);
         }
 
         /** @hide */
@@ -1334,7 +1425,7 @@
             if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
                 format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
             }
-            if (mMaxInputChannelCount == 1) {
+            if (getMaxInputChannelCount() == 1) {
                 // mono-only format
                 format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             }
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 20b16d7..6b78817 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -22,17 +22,24 @@
 
 #include "FilterClient.h"
 
-using ::aidl::android::media::tv::tuner::TunerFilterAvSettings;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddressSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterAlpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterIpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterMmtpConfiguration;
 using ::aidl::android::media::tv::tuner::TunerFilterMonitorEvent;
 using ::aidl::android::media::tv::tuner::TunerFilterScIndexMask;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionBits;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionCondition;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionTableInfo;
 using ::aidl::android::media::tv::tuner::TunerFilterSharedHandleInfo;
+using ::aidl::android::media::tv::tuner::TunerFilterTlvConfiguration;
 using ::aidl::android::media::tv::tuner::TunerFilterTsConfiguration;
 
-using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
 using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
-using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
+using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
 using ::android::hardware::tv::tuner::V1_0::DemuxStreamId;
+using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
 using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
@@ -318,42 +325,317 @@
 
 TunerFilterConfiguration FilterClient::getAidlFilterSettings(DemuxFilterSettings configure) {
     TunerFilterConfiguration config;
-    // TODO: complete filter setting conversion
     switch (configure.getDiscriminator()) {
-        case DemuxFilterSettings::hidl_discriminator::ts: {
-            TunerFilterSettings filterSettings;
-            switch (configure.ts().filterSettings.getDiscriminator()) {
-                case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::av: {
-                    TunerFilterAvSettings av{
-                        .isPassthrough = configure.ts().filterSettings.av().isPassthrough,
-                    };
-                    filterSettings.set<TunerFilterSettings::av>(av);
-                    break;
-                }
-                default:
-                    break;
-            }
-
-            TunerFilterTsConfiguration ts{
-                .tpid = configure.ts().tpid,
-                .filterSettings = filterSettings,
-            };
-            config.set<TunerFilterConfiguration::ts>(ts);
-
-            return config;
-        }
+        case DemuxFilterSettings::hidl_discriminator::ts:
+            return getAidlTsSettings(configure.ts());
         case DemuxFilterSettings::hidl_discriminator::mmtp:
-            break;
+            return getAidlMmtpSettings(configure.mmtp());
         case DemuxFilterSettings::hidl_discriminator::ip:
-            break;
+            return getAidlIpSettings(configure.ip());
         case DemuxFilterSettings::hidl_discriminator::tlv:
-            break;
+            return getAidlTlvSettings(configure.tlv());
+        case DemuxFilterSettings::hidl_discriminator::alp:
+            return getAidlAlpSettings(configure.alp());
         default:
             break;
     }
+    ALOGE("Wrong DemuxFilterSettings union.");
     return config;
 }
 
+TunerFilterConfiguration FilterClient::getAidlTsSettings(DemuxTsFilterSettings ts) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (ts.filterSettings.getDiscriminator()) {
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::av: {
+            filterSettings.set<TunerFilterSettings::av>(
+                    getAidlAvSettings(ts.filterSettings.av()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(ts.filterSettings.section()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+            filterSettings.set<TunerFilterSettings::pesData>(
+                    getAidlPesDataSettings(ts.filterSettings.pesData()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::record: {
+            filterSettings.set<TunerFilterSettings::record>(
+                    getAidlRecordSettings(ts.filterSettings.record()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterTsConfiguration aidlTs{
+        .tpid = static_cast<char16_t>(ts.tpid),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::ts>(aidlTs);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (mmtp.filterSettings.getDiscriminator()) {
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::av: {
+            filterSettings.set<TunerFilterSettings::av>(
+                    getAidlAvSettings(mmtp.filterSettings.av()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(mmtp.filterSettings.section()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+            filterSettings.set<TunerFilterSettings::pesData>(
+                    getAidlPesDataSettings(mmtp.filterSettings.pesData()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::record: {
+            filterSettings.set<TunerFilterSettings::record>(
+                    getAidlRecordSettings(mmtp.filterSettings.record()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::download: {
+            filterSettings.set<TunerFilterSettings::download>(
+                    getAidlDownloadSettings(mmtp.filterSettings.download()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterMmtpConfiguration aidlMmtp{
+        .mmtpPid = static_cast<char16_t>(mmtp.mmtpPid),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::mmtp>(aidlMmtp);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlIpSettings(DemuxIpFilterSettings ip) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (ip.filterSettings.getDiscriminator()) {
+        case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(ip.filterSettings.section()));
+            break;
+        }
+        case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+            filterSettings.set<TunerFilterSettings::isPassthrough>(
+                    ip.filterSettings.bPassthrough());
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerDemuxIpAddressSettings ipAddr{
+        .srcPort = static_cast<char16_t>(ip.ipAddr.srcPort),
+        .dstPort = static_cast<char16_t>(ip.ipAddr.dstPort),
+    };
+    getAidlIpAddress(ip.ipAddr, ipAddr.srcIpAddress, ipAddr.dstIpAddress);
+
+    TunerFilterIpConfiguration aidlIp{
+        .ipAddr = ipAddr,
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::ip>(aidlIp);
+
+    return config;
+}
+
+void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr,
+        TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress) {
+    switch (ipAddr.srcIpAddress.getDiscriminator()) {
+        case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
+            int size = ipAddr.srcIpAddress.v4().size();
+            srcIpAddress.isIpV6 = false;
+            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
+                    srcIpAddress.addr.begin());
+            break;
+        }
+        case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v6: {
+            int size = ipAddr.srcIpAddress.v6().size();
+            srcIpAddress.isIpV6 = true;
+            srcIpAddress.addr.resize(size);
+            copy(&ipAddr.srcIpAddress.v6()[0], &ipAddr.srcIpAddress.v6()[size],
+                    srcIpAddress.addr.begin());
+            break;
+        }
+        default:
+            break;
+    }
+    switch (ipAddr.dstIpAddress.getDiscriminator()) {
+        case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
+            int size = ipAddr.dstIpAddress.v4().size();
+            dstIpAddress.isIpV6 = false;
+            dstIpAddress.addr.resize(size);
+            copy(&ipAddr.dstIpAddress.v4()[0], &ipAddr.dstIpAddress.v4()[size],
+                    dstIpAddress.addr.begin());
+            break;
+        }
+        case DemuxIpAddress::DstIpAddress::hidl_discriminator::v6: {
+            int size = ipAddr.dstIpAddress.v6().size();
+            dstIpAddress.isIpV6 = true;
+            dstIpAddress.addr.resize(size);
+            copy(&ipAddr.dstIpAddress.v6()[0], &ipAddr.dstIpAddress.v6()[size],
+                    dstIpAddress.addr.begin());
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+TunerFilterConfiguration FilterClient::getAidlTlvSettings(DemuxTlvFilterSettings tlv) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (tlv.filterSettings.getDiscriminator()) {
+        case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(tlv.filterSettings.section()));
+            break;
+        }
+        case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+            filterSettings.set<TunerFilterSettings::isPassthrough>(
+                    tlv.filterSettings.bPassthrough());
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterTlvConfiguration aidlTlv{
+        .packetType = static_cast<int8_t>(tlv.packetType),
+        .isCompressedIpPacket = tlv.isCompressedIpPacket,
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::tlv>(aidlTlv);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlAlpSettings(DemuxAlpFilterSettings alp) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (alp.filterSettings.getDiscriminator()) {
+        case DemuxAlpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(alp.filterSettings.section()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterAlpConfiguration aidlAlp{
+        .packetType = static_cast<int8_t>(alp.packetType),
+        .lengthType = static_cast<int8_t>(alp.lengthType),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::alp>(aidlAlp);
+
+    return config;
+}
+
+TunerFilterAvSettings FilterClient::getAidlAvSettings(DemuxFilterAvSettings hidlAv) {
+    TunerFilterAvSettings aidlAv{
+        .isPassthrough = hidlAv.isPassthrough,
+    };
+    return aidlAv;
+}
+
+TunerFilterSectionSettings FilterClient::getAidlSectionSettings(
+        DemuxFilterSectionSettings hidlSection) {
+    TunerFilterSectionSettings aidlSection;
+
+    switch (hidlSection.condition.getDiscriminator()) {
+        case DemuxFilterSectionSettings::Condition::hidl_discriminator::sectionBits: {
+            TunerFilterSectionBits sectionBits;
+            auto hidlSectionBits = hidlSection.condition.sectionBits();
+            sectionBits.filter.resize(hidlSectionBits.filter.size());
+            sectionBits.mask.resize(hidlSectionBits.mask.size());
+            sectionBits.mode.resize(hidlSectionBits.mode.size());
+            copy(hidlSectionBits.filter.begin(), hidlSectionBits.filter.end(),
+                    hidlSectionBits.filter.begin());
+            copy(hidlSectionBits.mask.begin(), hidlSectionBits.mask.end(),
+                    hidlSectionBits.mask.begin());
+            copy(hidlSectionBits.mode.begin(), hidlSectionBits.mode.end(),
+                    hidlSectionBits.mode.begin());
+            aidlSection.condition.set<TunerFilterSectionCondition::sectionBits>(sectionBits);
+            break;
+        }
+        case DemuxFilterSectionSettings::Condition::hidl_discriminator::tableInfo: {
+            TunerFilterSectionTableInfo tableInfo{
+                .tableId = static_cast<char16_t>(hidlSection.condition.tableInfo().tableId),
+                .version = static_cast<char16_t>(hidlSection.condition.tableInfo().version),
+            };
+            aidlSection.condition.set<TunerFilterSectionCondition::tableInfo>(tableInfo);
+            break;
+        }
+    }
+    aidlSection.isCheckCrc = hidlSection.isCheckCrc;
+    aidlSection.isRepeat = hidlSection.isRepeat;
+    aidlSection.isRaw = hidlSection.isRaw;
+    return aidlSection;
+}
+
+TunerFilterPesDataSettings FilterClient::getAidlPesDataSettings(
+        DemuxFilterPesDataSettings hidlPesData) {
+    TunerFilterPesDataSettings aidlPesData{
+        .streamId = static_cast<char16_t>(hidlPesData.streamId),
+        .isRaw = hidlPesData.isRaw,
+    };
+    return aidlPesData;
+}
+
+TunerFilterRecordSettings FilterClient::getAidlRecordSettings(
+        DemuxFilterRecordSettings hidlRecord) {
+    TunerFilterScIndexMask mask;
+    switch (hidlRecord.scIndexMask.getDiscriminator()) {
+        case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::sc: {
+            mask.set<TunerFilterScIndexMask::sc>(hidlRecord.scIndexMask.sc());
+            break;
+        }
+        case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::scHevc: {
+            mask.set<TunerFilterScIndexMask::scHevc>(hidlRecord.scIndexMask.scHevc());
+            break;
+        }
+        default:
+            break;
+    }
+    TunerFilterRecordSettings aidlRecord{
+        .tsIndexMask = static_cast<int32_t>(hidlRecord.tsIndexMask),
+        .scIndexType = static_cast<int32_t>(hidlRecord.scIndexType),
+        .scIndexMask = mask,
+    };
+    return aidlRecord;
+}
+
+TunerFilterDownloadSettings FilterClient::getAidlDownloadSettings(
+        DemuxFilterDownloadSettings hidlDownload) {
+    TunerFilterDownloadSettings aidlDownload{
+        .downloadId = static_cast<int32_t>(hidlDownload.downloadId),
+    };
+    return aidlDownload;
+}
 
 void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
         DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 89eee3c..21919ac 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -33,8 +33,14 @@
 using Status = ::ndk::ScopedAStatus;
 using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
 using ::aidl::android::media::tv::tuner::ITunerFilter;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddress;
+using ::aidl::android::media::tv::tuner::TunerFilterAvSettings;
 using ::aidl::android::media::tv::tuner::TunerFilterConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterDownloadSettings;
 using ::aidl::android::media::tv::tuner::TunerFilterEvent;
+using ::aidl::android::media::tv::tuner::TunerFilterPesDataSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterRecordSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionSettings;
 using ::aidl::android::media::tv::tuner::TunerFilterSettings;
 
 using ::android::hardware::EventFlag;
@@ -43,8 +49,19 @@
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 using ::android::hardware::hidl_handle;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::IFilter;
 using ::android::hardware::tv::tuner::V1_0::Result;
 using ::android::hardware::tv::tuner::V1_1::AvStreamType;
@@ -201,6 +218,21 @@
 
 private:
     TunerFilterConfiguration getAidlFilterSettings(DemuxFilterSettings configure);
+
+    TunerFilterConfiguration getAidlTsSettings(DemuxTsFilterSettings configure);
+    TunerFilterConfiguration getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp);
+    TunerFilterConfiguration getAidlIpSettings(DemuxIpFilterSettings ip);
+    TunerFilterConfiguration getAidlTlvSettings(DemuxTlvFilterSettings tlv);
+    TunerFilterConfiguration getAidlAlpSettings(DemuxAlpFilterSettings alp);
+
+    TunerFilterAvSettings getAidlAvSettings(DemuxFilterAvSettings hidlAv);
+    TunerFilterSectionSettings getAidlSectionSettings(DemuxFilterSectionSettings hidlSection);
+    TunerFilterPesDataSettings getAidlPesDataSettings(DemuxFilterPesDataSettings hidlPesData);
+    TunerFilterRecordSettings getAidlRecordSettings(DemuxFilterRecordSettings hidlRecord);
+    TunerFilterDownloadSettings getAidlDownloadSettings(DemuxFilterDownloadSettings hidlDownload);
+
+    void getAidlIpAddress(DemuxIpAddress ipAddr,
+            TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress);
     Result getFilterMq();
     int copyData(uint8_t* buffer, int size);
     void checkIsMediaFilter(DemuxFilterType type);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ca9dcd6..5af0244 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -116,7 +116,6 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
     <uses-permission android:name="android.permission.INPUT_CONSUMER" />
-    <uses-permission android:name="android.permission.USE_BACKGROUND_BLUR" />
 
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index fc68a64..2a055fc 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -39,6 +39,25 @@
         tools:minHeight="100dp"
         tools:minWidth="100dp" />
 
+    <com.android.systemui.screenshot.CropView
+        android:id="@+id/crop_view"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginVertical="8dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="@id/guideline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:handleThickness="3dp"
+        app:handleColor="@*android:color/accent_device_default"
+        app:scrimColor="#9444"
+        tools:background="?android:colorBackground"
+        tools:minHeight="100dp"
+        tools:minWidth="100dp" />
+
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/guideline"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view.xml
new file mode 100644
index 0000000..380dd85
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_animation_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.biometrics.UdfpsAnimationView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/udfps_surface_view.xml b/packages/SystemUI/res/layout/udfps_surface_view.xml
new file mode 100644
index 0000000..18858d6
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_surface_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.biometrics.UdfpsSurfaceView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_surface_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 4059b49..e2fe223 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -171,5 +171,11 @@
     <declare-styleable name="PagedTileLayout">
         <attr name="sideLabels" format="boolean"/>
     </declare-styleable>
+
+    <declare-styleable name="CropView">
+        <attr name="handleThickness" format="dimension" />
+        <attr name="handleColor" format="color" />
+        <attr name="scrimColor" format="color" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index eee0128..81b7a7b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2580,9 +2580,6 @@
     <!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
     <string name="music_controls_no_title">No title</string>
 
-    <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
-    <string name="restart_button_description">Tap to restart this app and go full screen.</string>
-
     <!-- Action in accessibility menu to move the stack of bubbles [CHAR LIMIT=20] -->
     <string name="bubble_accessibility_action_move">Move</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 5384ddf..7a52d27 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -16,284 +16,27 @@
 
 package com.android.systemui;
 
-import android.app.ActivityClient;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-import java.lang.ref.WeakReference;
 
 import javax.inject.Inject;
 
-/** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
+/**
+ * Shows a restart-activity button when the foreground activity is in size compatibility mode.
+ *
+ * // TODO remove this class after cleanup all dependencies.
+ * @deprecated Use {@link com.android.wm.shell.sizecompatui.SizeCompatUIController}
+ */
+@Deprecated
 @SysUISingleton
-public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
-    private static final String TAG = "SizeCompatMode";
+public class SizeCompatModeActivityController extends SystemUI {
 
-    /** The showing buttons by display id. */
-    private final SparseArray<RestartActivityButton> mActiveButtons = new SparseArray<>(1);
-    /** Avoid creating display context frequently for non-default display. */
-    private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
-    private final CommandQueue mCommandQueue;
-
-    /** Only show once automatically in the process life. */
-    private boolean mHasShownHint;
-
-    @VisibleForTesting
     @Inject
-    SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
-            CommandQueue commandQueue) {
+    SizeCompatModeActivityController(Context context) {
         super(context);
-        mCommandQueue = commandQueue;
-        listeners.registerTaskStackListener(new TaskStackChangeListener() {
-            @Override
-            public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-                // Note the callback already runs on main thread.
-                updateRestartButton(displayId, activityToken);
-            }
-        });
     }
 
     @Override
-    public void start() {
-        mCommandQueue.addCallback(this);
-    }
-
-    @Override
-    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
-            boolean showImeSwitcher) {
-        RestartActivityButton button = mActiveButtons.get(displayId);
-        if (button == null) {
-            return;
-        }
-        boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
-        int newVisibility = imeShown ? View.GONE : View.VISIBLE;
-        // Hide the button when input method is showing.
-        if (button.getVisibility() != newVisibility) {
-            button.setVisibility(newVisibility);
-        }
-    }
-
-    @Override
-    public void onDisplayRemoved(int displayId) {
-        mDisplayContextCache.remove(displayId);
-        removeRestartButton(displayId);
-    }
-
-    private void removeRestartButton(int displayId) {
-        RestartActivityButton button = mActiveButtons.get(displayId);
-        if (button != null) {
-            button.remove();
-            mActiveButtons.remove(displayId);
-        }
-    }
-
-    private void updateRestartButton(int displayId, IBinder activityToken) {
-        if (activityToken == null) {
-            // Null token means the current foreground activity is not in size compatibility mode.
-            removeRestartButton(displayId);
-            return;
-        }
-
-        RestartActivityButton restartButton = mActiveButtons.get(displayId);
-        if (restartButton != null) {
-            restartButton.updateLastTargetActivity(activityToken);
-            return;
-        }
-
-        Context context = getOrCreateDisplayContext(displayId);
-        if (context == null) {
-            Log.i(TAG, "Cannot get context for display " + displayId);
-            return;
-        }
-
-        restartButton = createRestartButton(context);
-        restartButton.updateLastTargetActivity(activityToken);
-        if (restartButton.show()) {
-            mActiveButtons.append(displayId, restartButton);
-        } else {
-            onDisplayRemoved(displayId);
-        }
-    }
-
-    @VisibleForTesting
-    RestartActivityButton createRestartButton(Context context) {
-        RestartActivityButton button = new RestartActivityButton(context, mHasShownHint);
-        mHasShownHint = true;
-        return button;
-    }
-
-    private Context getOrCreateDisplayContext(int displayId) {
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            return mContext;
-        }
-        Context context = null;
-        WeakReference<Context> ref = mDisplayContextCache.get(displayId);
-        if (ref != null) {
-            context = ref.get();
-        }
-        if (context == null) {
-            Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
-            if (display != null) {
-                context = mContext.createDisplayContext(display);
-                mDisplayContextCache.put(displayId, new WeakReference<Context>(context));
-            }
-        }
-        return context;
-    }
-
-    @VisibleForTesting
-    static class RestartActivityButton extends ImageButton implements View.OnClickListener,
-            View.OnLongClickListener {
-
-        final WindowManager.LayoutParams mWinParams;
-        final boolean mShouldShowHint;
-        IBinder mLastActivityToken;
-
-        final int mPopupOffsetX;
-        final int mPopupOffsetY;
-        PopupWindow mShowingHint;
-
-        RestartActivityButton(Context context, boolean hasShownHint) {
-            super(context);
-            mShouldShowHint = !hasShownHint;
-            Drawable drawable = context.getDrawable(R.drawable.btn_restart);
-            setImageDrawable(drawable);
-            setContentDescription(context.getString(R.string.restart_button_description));
-
-            int drawableW = drawable.getIntrinsicWidth();
-            int drawableH = drawable.getIntrinsicHeight();
-            mPopupOffsetX = drawableW / 2;
-            mPopupOffsetY = drawableH * 2;
-
-            ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
-            GradientDrawable mask = new GradientDrawable();
-            mask.setShape(GradientDrawable.OVAL);
-            mask.setColor(color);
-            setBackground(new RippleDrawable(color, null /* content */, mask));
-            setOnClickListener(this);
-            setOnLongClickListener(this);
-
-            mWinParams = new WindowManager.LayoutParams();
-            mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
-            mWinParams.width = drawableW * 2;
-            mWinParams.height = drawableH * 2;
-            mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-            mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            mWinParams.format = PixelFormat.TRANSLUCENT;
-            mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-            mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
-                    + context.getDisplayId());
-        }
-
-        void updateLastTargetActivity(IBinder activityToken) {
-            mLastActivityToken = activityToken;
-        }
-
-        /** @return {@code false} if the target display is invalid. */
-        boolean show() {
-            try {
-                getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
-            } catch (WindowManager.InvalidDisplayException e) {
-                // The target display may have been removed when the callback has just arrived.
-                Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
-                return false;
-            }
-            return true;
-        }
-
-        void remove() {
-            if (mShowingHint != null) {
-                mShowingHint.dismiss();
-            }
-            getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
-        }
-
-        @Override
-        public void onClick(View v) {
-            ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
-        }
-
-        @Override
-        public boolean onLongClick(View v) {
-            showHint();
-            return true;
-        }
-
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-            if (mShouldShowHint) {
-                showHint();
-            }
-        }
-
-        @Override
-        public void setLayoutDirection(int layoutDirection) {
-            int gravity = getGravity(layoutDirection);
-            if (mWinParams.gravity != gravity) {
-                mWinParams.gravity = gravity;
-                if (mShowingHint != null) {
-                    mShowingHint.dismiss();
-                    showHint();
-                }
-                getContext().getSystemService(WindowManager.class).updateViewLayout(this,
-                        mWinParams);
-            }
-            super.setLayoutDirection(layoutDirection);
-        }
-
-        void showHint() {
-            if (mShowingHint != null) {
-                return;
-            }
-
-            View popupView = LayoutInflater.from(getContext()).inflate(
-                    R.layout.size_compat_mode_hint, null /* root */);
-            PopupWindow popupWindow = new PopupWindow(popupView,
-                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
-            popupWindow.setWindowLayoutType(mWinParams.type);
-            popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
-            popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
-            popupWindow.setClippingEnabled(false);
-            popupWindow.setOnDismissListener(() -> mShowingHint = null);
-            mShowingHint = popupWindow;
-
-            Button gotItButton = popupView.findViewById(R.id.got_it);
-            gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
-                    null /* content */, null /* mask */));
-            gotItButton.setOnClickListener(view -> popupWindow.dismiss());
-            popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
-        }
-
-        private static int getGravity(int layoutDirection) {
-            return Gravity.BOTTOM
-                    | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
-        }
-    }
+    public void start() { }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
new file mode 100644
index 0000000..2083f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.view.Surface;
+
+/**
+ * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
+ * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
+ * illumination is no longer necessary.
+ */
+public interface HbmCallback {
+    /**
+     * UdfpsView will call this to enable the HBM before drawing the illumination dot.
+     *
+     * @param surface A valid surface for which the HBM should be enabled.
+     */
+    void enableHbm(@NonNull Surface surface);
+
+    /**
+     * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+     *
+     * @param surface A valid surface for which the HBM should be disabled.
+     */
+    void disableHbm(@NonNull Surface surface);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 40fe7b1..3ea8140 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -52,4 +52,18 @@
     public void setAlpha(int alpha) {
         mFingerprintDrawable.setAlpha(alpha);
     }
+
+    /**
+     * @return The amount of padding that's needed on each side of the sensor, in pixels.
+     */
+    public int getPaddingX() {
+        return 0;
+    }
+
+    /**
+     * @return The amount of padding that's needed on each side of the sensor, in pixels.
+     */
+    public int getPaddingY() {
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
index 52662ae..68f1414 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
@@ -70,6 +70,9 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
+        canvas.save();
+        canvas.translate(getPaddingX(), getPaddingY());
+
         final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_YES) != 0;
         if (!isNightMode) {
@@ -78,6 +81,18 @@
             }
         }
         mFingerprintDrawable.draw(canvas);
+
+        canvas.restore();
+    }
+
+    @Override
+    public int getPaddingX() {
+        return (int) Math.ceil(SHADOW_RADIUS);
+    }
+
+    @Override
+    public int getPaddingY() {
+        return (int) Math.ceil(SHADOW_RADIUS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
new file mode 100644
index 0000000..fded737
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt,
+ * FingerprintManager).
+ */
+public class UdfpsAnimationView extends View implements DozeReceiver,
+        ScrimController.ScrimChangedListener {
+
+    private static final String TAG = "UdfpsAnimationView";
+
+    @NonNull private UdfpsView mParent;
+    @Nullable private UdfpsAnimation mUdfpsAnimation;
+    @NonNull private RectF mSensorRect;
+    private int mNotificationPanelAlpha;
+
+
+    public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mSensorRect = new RectF();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mUdfpsAnimation != null) {
+            final int alpha = mParent.shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255;
+            mUdfpsAnimation.setAlpha(alpha);
+            mUdfpsAnimation.draw(canvas);
+        }
+    }
+
+    void setParent(@NonNull UdfpsView parent) {
+        mParent = parent;
+    }
+
+    void setAnimation(@Nullable UdfpsAnimation animation) {
+        mUdfpsAnimation = animation;
+    }
+
+    void onSensorRectUpdated(@NonNull RectF sensorRect) {
+        mSensorRect = sensorRect;
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.onSensorRectUpdated(mSensorRect);
+        }
+    }
+
+    void updateColor() {
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.updateColor();
+        }
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        if (mUdfpsAnimation instanceof DozeReceiver) {
+            ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
+        }
+    }
+
+    @Override
+    public void onAlphaChanged(float alpha) {
+        mNotificationPanelAlpha = (int) (alpha * 255);
+        postInvalidate();
+    }
+
+    void onEnrollmentProgress(int remaining) {
+        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining);
+        }
+    }
+
+    void onEnrollmentHelp() {
+        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
+            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 008e8f5..ac4b93a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -62,7 +62,7 @@
  */
 @SuppressWarnings("deprecation")
 @SysUISingleton
-public class UdfpsController implements UdfpsView.HbmCallback, DozeReceiver {
+public class UdfpsController implements DozeReceiver, HbmCallback {
     private static final String TAG = "UdfpsController";
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
 
@@ -242,12 +242,15 @@
         }
     }
 
-    private WindowManager.LayoutParams computeLayoutParams() {
+    private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) {
+        final int paddingX = animation != null ? animation.getPaddingX() : 0;
+        final int paddingY = animation != null ? animation.getPaddingY() : 0;
+
         // Default dimensions assume portrait mode.
-        mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
-        mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
-        mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius;
-        mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius;
+        mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
+        mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
+        mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
+        mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
 
         Point p = new Point();
         // Gets the size based on the current rotation of the display.
@@ -289,8 +292,9 @@
             if (!mIsOverlayShowing) {
                 try {
                     Log.v(TAG, "showUdfpsOverlay | adding window");
-                    mView.setUdfpsAnimation(getUdfpsAnimationForReason(reason));
-                    mWindowManager.addView(mView, computeLayoutParams());
+                    final UdfpsAnimation animation = getUdfpsAnimationForReason(reason);
+                    mView.setUdfpsAnimation(animation);
+                    mWindowManager.addView(mView, computeLayoutParams(animation));
                     mView.setOnTouchListener(mOnTouchListener);
                     mIsOverlayShowing = true;
                 } catch (RuntimeException e) {
@@ -374,9 +378,8 @@
 
     // This method can be called from the UI thread.
     private void onFingerDown(int x, int y, float minor, float major) {
-        mView.setOnIlluminatedRunnable(
-                () -> mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
-        mView.startIllumination();
+        mView.startIllumination(() ->
+                mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
     }
 
     // This method can be called from the UI thread.
@@ -386,13 +389,13 @@
     }
 
     @Override
-    public void enableHbm(Surface surface) {
+    public void enableHbm(@NonNull Surface surface) {
         // Do nothing. This method can be implemented for devices that require the high-brightness
         // mode for fingerprint illumination.
     }
 
     @Override
-    public void disableHbm(Surface surface) {
+    public void disableHbm(@NonNull Surface surface) {
         // Do nothing. This method can be implemented for devices that require the high-brightness
         // mode for fingerprint illumination.
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
new file mode 100644
index 0000000..8bea05b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.Nullable;
+
+/**
+ * Interface that should be implemented by UI's that need to coordinate user touches,
+ * views/animations, and modules that start/stop display illumination.
+ */
+interface UdfpsIlluminator {
+    /**
+     * @param callback Invoked when HBM should be enabled or disabled.
+     */
+    void setHbmCallback(@Nullable HbmCallback callback);
+
+    /**
+     * Invoked when illumination should start.
+     * @param onIlluminatedRunnable Invoked when the display has been illuminated.
+     */
+    void startIllumination(@Nullable Runnable onIlluminatedRunnable);
+
+    /**
+     * Invoked when illumination should end.
+     */
+    void stopIllumination();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
new file mode 100644
index 0000000..97c215e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
+ * only. All other animations should be done on the other view.
+ */
+public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
+    private static final String TAG = "UdfpsSurfaceView";
+
+    /**
+     * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
+     * several abstract methods that are not used here but require implementation.
+     */
+    private interface SimpleDrawable {
+        void draw(Canvas canvas);
+    }
+
+    @NonNull private final SurfaceHolder mHolder;
+    @NonNull private final Paint mSensorPaint;
+    @NonNull private final SimpleDrawable mIlluminationDotDrawable;
+
+    @NonNull private RectF mSensorRect;
+    @Nullable private HbmCallback mHbmCallback;
+
+    public UdfpsSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mHolder = getHolder();
+        mHolder.setFormat(PixelFormat.RGBA_8888);
+
+        mSensorRect = new RectF();
+        mSensorPaint = new Paint(0 /* flags */);
+        mSensorPaint.setAntiAlias(true);
+        mSensorPaint.setARGB(255, 255, 255, 255);
+        mSensorPaint.setStyle(Paint.Style.FILL);
+
+        mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
+    }
+
+    @Override
+    public void setHbmCallback(@Nullable HbmCallback callback) {
+        mHbmCallback = callback;
+    }
+
+    @Override
+    public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
+        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+            mHbmCallback.enableHbm(mHolder.getSurface());
+        }
+        drawImmediately(mIlluminationDotDrawable);
+
+        if (onIlluminatedRunnable != null) {
+            // No framework API can reliably tell when a frame reaches the panel. A timeout is the
+            // safest solution. The frame should be displayed within 3 refresh cycles, which on a
+            // 60 Hz panel equates to 50 milliseconds.
+            postDelayed(onIlluminatedRunnable, 50 /* delayMillis */);
+        }
+    }
+
+    @Override
+    public void stopIllumination() {
+        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+            mHbmCallback.disableHbm(mHolder.getSurface());
+        }
+
+        invalidate();
+    }
+
+    void onSensorRectUpdated(@NonNull RectF sensorRect) {
+        mSensorRect = sensorRect;
+    }
+
+    /**
+     * Immediately draws the provided drawable on this SurfaceView's surface.
+     */
+    private void drawImmediately(@NonNull SimpleDrawable drawable) {
+        Canvas canvas = null;
+        try {
+            canvas = mHolder.lockCanvas();
+            drawable.draw(canvas);
+        } finally {
+            // Make sure the surface is never left in a bad state.
+            if (canvas != null) {
+                mHolder.unlockCanvasAndPost(canvas);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 4c05b88..d448ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -27,16 +27,14 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.RectF;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeReceiver;
@@ -44,56 +42,19 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 
 /**
- * A full screen view with a configurable illumination dot and scrim.
+ * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
+ * animations.
  */
-public class UdfpsView extends SurfaceView implements DozeReceiver,
+public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator,
         StatusBarStateController.StateListener, ScrimController.ScrimChangedListener {
     private static final String TAG = "UdfpsView";
 
-    /**
-     * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
-     * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
-     * illumination is no longer necessary.
-     */
-    interface HbmCallback {
-        /**
-         * UdfpsView will call this to enable the HBM before drawing the illumination dot.
-         *
-         * @param surface A valid surface for which the HBM should be enabled.
-         */
-        void enableHbm(@NonNull Surface surface);
-
-        /**
-         * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
-         *
-         * @param surface A valid surface for which the HBM should be disabled.
-         */
-        void disableHbm(@NonNull Surface surface);
-    }
-
-    /**
-     * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
-     * several abstract methods that are not used here but require implementation.
-     */
-    private interface SimpleDrawable {
-        void draw(Canvas canvas);
-    }
-
-    // Radius in pixels.
-    private static final float SENSOR_SHADOW_RADIUS = 2.0f;
-
     private static final int DEBUG_TEXT_SIZE_PX = 32;
 
-    @NonNull private final SurfaceHolder mHolder;
+    @NonNull private final UdfpsSurfaceView mHbmSurfaceView;
+    @NonNull private final UdfpsAnimationView mAnimationView;
     @NonNull private final RectF mSensorRect;
-    @NonNull private final Paint mSensorPaint;
     @NonNull private final Paint mDebugTextPaint;
-    @NonNull private final SimpleDrawable mIlluminationDotDrawable;
-    @NonNull private final SimpleDrawable mClearSurfaceDrawable;
-
-    @Nullable private UdfpsAnimation mUdfpsAnimation;
-    @Nullable private HbmCallback mHbmCallback;
-    @Nullable private Runnable mOnIlluminatedRunnable;
 
     // Used to obtain the sensor location.
     @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
@@ -103,7 +64,6 @@
     private boolean mIlluminationRequested;
     private int mStatusBarState;
     private boolean mNotificationShadeExpanded;
-    private int mNotificationPanelAlpha;
 
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -121,27 +81,27 @@
             a.recycle();
         }
 
-        mHolder = getHolder();
-        mHolder.setFormat(PixelFormat.RGBA_8888);
+        // Inflate UdfpsSurfaceView
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view,
+                null, false);
+        addView(mHbmSurfaceView);
+        mHbmSurfaceView.setVisibility(View.INVISIBLE);
+
+        // Inflate UdfpsAnimationView
+        mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view,
+                null, false);
+        mAnimationView.setParent(this);
+        addView(mAnimationView);
 
         mSensorRect = new RectF();
-        mSensorPaint = new Paint(0 /* flags */);
-        mSensorPaint.setAntiAlias(true);
-        mSensorPaint.setARGB(255, 255, 255, 255);
-        mSensorPaint.setStyle(Paint.Style.FILL);
 
         mDebugTextPaint = new Paint();
         mDebugTextPaint.setAntiAlias(true);
         mDebugTextPaint.setColor(Color.BLUE);
         mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
 
-        mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
-        mClearSurfaceDrawable = canvas -> canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-
         mIlluminationRequested = false;
-        // SurfaceView sets this to true by default. We must set it to false to allow
-        // onDraw to be called.
-        setWillNotDraw(false);
     }
 
     void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
@@ -149,29 +109,17 @@
     }
 
     void setUdfpsAnimation(@Nullable UdfpsAnimation animation) {
-        mUdfpsAnimation = animation;
+        mAnimationView.setAnimation(animation);
     }
 
-    /**
-     * Sets a callback that can be used to enable and disable the high-brightness mode (HBM).
-     */
-    void setHbmCallback(@Nullable HbmCallback callback) {
-        mHbmCallback = callback;
-    }
-
-    /**
-     * Sets a runnable that will be run when the first illumination frame reaches the panel.
-     * The runnable is reset to null after it is executed once.
-     */
-    void setOnIlluminatedRunnable(Runnable runnable) {
-        mOnIlluminatedRunnable = runnable;
+    @Override
+    public void setHbmCallback(@Nullable HbmCallback callback) {
+        mHbmSurfaceView.setHbmCallback(callback);
     }
 
     @Override
     public void dozeTimeTick() {
-        if (mUdfpsAnimation instanceof DozeReceiver) {
-            ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
-        }
+        mAnimationView.dozeTimeTick();
     }
 
     @Override
@@ -186,17 +134,15 @@
 
     @Override
     public void onAlphaChanged(float alpha) {
-        mNotificationPanelAlpha = (int) (alpha * 255);
-        postInvalidate();
+        mAnimationView.onAlphaChanged(alpha);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         mSensorRect.set(0, 0, 2 * mSensorProps.sensorRadius, 2 * mSensorProps.sensorRadius);
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.onSensorRectUpdated(new RectF(mSensorRect));
-        }
+        mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect));
+        mAnimationView.onSensorRectUpdated(new RectF(mSensorRect));
     }
 
     @Override
@@ -205,13 +151,7 @@
         Log.v(TAG, "onAttachedToWindow");
 
         // Retrieve the colors each time, since it depends on day/night mode
-        updateColor();
-    }
-
-    private void updateColor() {
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.updateColor();
-        }
+        mAnimationView.updateColor();
     }
 
     @Override
@@ -220,25 +160,6 @@
         Log.v(TAG, "onDetachedFromWindow");
     }
 
-    /**
-     * Immediately draws the provided drawable on this SurfaceView's surface.
-     */
-    private void drawImmediately(@NonNull SimpleDrawable drawable) {
-        Canvas canvas = null;
-        try {
-            canvas = mHolder.lockCanvas();
-            drawable.draw(canvas);
-        } finally {
-            // Make sure the surface is never left in a bad state.
-            if (canvas != null) {
-                mHolder.unlockCanvasAndPost(canvas);
-            }
-        }
-    }
-
-    /**
-     * This onDraw will not execute if setWillNotDraw(true) is called.
-     */
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
@@ -246,11 +167,6 @@
             if (!TextUtils.isEmpty(mDebugMessage)) {
                 canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
             }
-            if (mUdfpsAnimation != null) {
-                final int alpha = shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255;
-                mUdfpsAnimation.setAlpha(alpha);
-                mUdfpsAnimation.draw(canvas);
-            }
         }
     }
 
@@ -283,7 +199,7 @@
      * authentication which would cause the UDFPS icons to abruptly disappear, do it here by not
      * sending onFingerDown and smoothly animating away.
      */
-    private boolean shouldPauseAuth() {
+    boolean shouldPauseAuth() {
         return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD)
                 || mStatusBarState == SHADE_LOCKED
                 || mStatusBarState == FULLSCREEN_USER_SWITCHER;
@@ -293,49 +209,30 @@
         return mIlluminationRequested;
     }
 
-    void startIllumination() {
+    /**
+     * @param onIlluminatedRunnable Runs when the first illumination frame reaches the panel.
+     */
+    @Override
+    public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
         mIlluminationRequested = true;
-
-        // Disable onDraw to prevent overriding the illumination dot with the regular UI.
-        setWillNotDraw(true);
-
-        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
-            mHbmCallback.enableHbm(mHolder.getSurface());
-        }
-        drawImmediately(mIlluminationDotDrawable);
-
-        if (mOnIlluminatedRunnable != null) {
-            // No framework API can reliably tell when a frame reaches the panel. A timeout is the
-            // safest solution. The frame should be displayed within 3 refresh cycles, which on a
-            // 60 Hz panel equates to 50 milliseconds.
-            postDelayed(mOnIlluminatedRunnable, 50 /* delayMillis */);
-            mOnIlluminatedRunnable = null;
-        }
+        mAnimationView.setVisibility(View.INVISIBLE);
+        mHbmSurfaceView.setVisibility(View.VISIBLE);
+        mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
     }
 
-    void stopIllumination() {
+    @Override
+    public void stopIllumination() {
         mIlluminationRequested = false;
-
-        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
-            mHbmCallback.disableHbm(mHolder.getSurface());
-        }
-        // It may be necessary to clear the surface for the HBM changes to apply.
-        drawImmediately(mClearSurfaceDrawable);
-
-        // Enable onDraw to allow the regular UI to be drawn.
-        setWillNotDraw(false);
-        invalidate();
+        mAnimationView.setVisibility(View.VISIBLE);
+        mHbmSurfaceView.setVisibility(View.INVISIBLE);
+        mHbmSurfaceView.stopIllumination();
     }
 
     void onEnrollmentProgress(int remaining) {
-        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
-            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining);
-        }
+        mAnimationView.onEnrollmentProgress(remaining);
     }
 
     void onEnrollmentHelp() {
-        if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) {
-            ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp();
-        }
+        mAnimationView.onEnrollmentHelp();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index fcb5da3..4384610 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -545,6 +545,7 @@
         }
         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+        mNavigationBarView.setBehavior(mBehavior);
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
         mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
 
@@ -919,6 +920,9 @@
         }
         if (mBehavior != behavior) {
             mBehavior = behavior;
+            if (mNavigationBarView != null) {
+                mNavigationBarView.setBehavior(behavior);
+            }
             updateSystemUiStateFlags(-1);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index e7f2b222..c07404c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -57,6 +57,7 @@
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
+import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -626,6 +627,10 @@
         mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
     }
 
+    public void setBehavior(@Behavior int behavior) {
+        mRotationButtonController.onBehaviorChanged(behavior);
+    }
+
     @Override
     public void setLayoutDirection(int layoutDirection) {
         reloadNavIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index df9e7a4..33d1807 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -35,6 +35,8 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
@@ -78,6 +80,7 @@
     private Consumer<Integer> mRotWatcherListener;
     private boolean mListenersRegistered = false;
     private boolean mIsNavigationBarShowing;
+    private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
     private boolean mSkipOverrideUserLockPrefsOnce;
     private int mLightIconColor;
     private int mDarkIconColor;
@@ -297,8 +300,8 @@
         }
         mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
 
-        if (mIsNavigationBarShowing) {
-            // The navbar is visible so show the icon right away
+        if (canShowRotationButton()) {
+            // The navbar is visible / it's in visual immersive mode, so show the icon right away
             showAndLogRotationSuggestion();
         } else {
             // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
@@ -318,14 +321,28 @@
     void onNavigationBarWindowVisibilityChange(boolean showing) {
         if (mIsNavigationBarShowing != showing) {
             mIsNavigationBarShowing = showing;
-
-            // If the navbar is visible, show the rotate button if there's a pending suggestion
-            if (showing && mPendingRotationSuggestion) {
-                showAndLogRotationSuggestion();
-            }
+            showPendingRotationButtonIfNeeded();
         }
     }
 
+    void onBehaviorChanged(@Behavior int behavior) {
+        if (mBehavior != behavior) {
+            mBehavior = behavior;
+            showPendingRotationButtonIfNeeded();
+        }
+    }
+
+    private void showPendingRotationButtonIfNeeded() {
+        if (canShowRotationButton() && mPendingRotationSuggestion) {
+            showAndLogRotationSuggestion();
+        }
+    }
+
+    /** Return true when either the nav bar is visible or it's in visual immersive mode. */
+    private boolean canShowRotationButton() {
+        return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+    }
+
     public Context getContext() {
         return mContext;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index b6e07b1..3841dac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -49,9 +50,10 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
-                activityStarter, qsLogger, sensorPrivacyController);
+                activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 9cc6f09..2f0071a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -49,9 +50,10 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
-                activityStarter, qsLogger, sensorPrivacyController);
+                activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 12205d6..00703e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 /**
  * Superclass to toggle individual sensor privacy via quick settings tiles
@@ -42,6 +43,7 @@
 public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanState> implements
         IndividualSensorPrivacyController.Callback {
 
+    private final KeyguardStateController mKeyguard;
     private IndividualSensorPrivacyController mSensorPrivacyController;
 
     /**
@@ -61,10 +63,12 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                 activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
+        mKeyguard = keyguardStateController;
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -75,6 +79,13 @@
 
     @Override
     protected void handleClick() {
+        if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
+            mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+                mSensorPrivacyController.setSensorBlocked(getSensorId(),
+                        !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+            });
+            return;
+        }
         mSensorPrivacyController.setSensorBlocked(getSensorId(),
                 !mSensorPrivacyController.isSensorBlocked(getSensorId()));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
new file mode 100644
index 0000000..8e182b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * CropView has top and bottom draggable crop handles, with a scrim to darken the areas being
+ * cropped out.
+ */
+public class CropView extends View {
+    private enum CropBoundary {
+        NONE, TOP, BOTTOM
+    }
+
+    private final float mCropTouchMargin;
+    private final Paint mShadePaint;
+    private final Paint mHandlePaint;
+
+    // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
+    // view, respectively.
+    private float mTopCrop = 0f;
+    private float mBottomCrop = 1f;
+
+    private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+    private float mLastY;
+
+    public CropView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        TypedArray t = context.getTheme().obtainStyledAttributes(
+                attrs, R.styleable.CropView, 0, 0);
+        mShadePaint = new Paint();
+        mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
+        mHandlePaint = new Paint();
+        mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+        mHandlePaint.setStrokeWidth(
+                t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
+        t.recycle();
+        // 48 dp touchable region around each handle.
+        mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        drawShade(canvas, 0, mTopCrop);
+        drawShade(canvas, mBottomCrop, 1f);
+        drawHandle(canvas, mTopCrop);
+        drawHandle(canvas, mBottomCrop);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int topPx = fractionToPixels(mTopCrop);
+        int bottomPx = fractionToPixels(mBottomCrop);
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+            if (mCurrentDraggingBoundary != CropBoundary.NONE) {
+                mLastY = event.getY();
+            }
+            return true;
+        }
+        if (event.getAction() == MotionEvent.ACTION_MOVE
+                && mCurrentDraggingBoundary != CropBoundary.NONE) {
+            float delta = event.getY() - mLastY;
+            if (mCurrentDraggingBoundary == CropBoundary.TOP) {
+                mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0,
+                        bottomPx - 2 * mCropTouchMargin));
+            } else {  // Bottom
+                mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta,
+                        topPx + 2 * mCropTouchMargin, getMeasuredHeight()));
+            }
+            mLastY = event.getY();
+            invalidate();
+            return true;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    /**
+     * @return value [0,1] representing the position of the top crop boundary.
+     */
+    public float getTopBoundary() {
+        return mTopCrop;
+    }
+
+    /**
+     * @return value [0,1] representing the position of the bottom crop boundary.
+     */
+    public float getBottomBoundary() {
+        return mBottomCrop;
+    }
+
+    private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
+        canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(),
+                fractionToPixels(fracEnd), mShadePaint);
+    }
+
+    private void drawHandle(Canvas canvas, float frac) {
+        int y = fractionToPixels(frac);
+        canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint);
+    }
+
+    private int fractionToPixels(float frac) {
+        return (int) (frac * getMeasuredHeight());
+    }
+
+    private float pixelsToFraction(int px) {
+        return px / (float) getMeasuredHeight();
+    }
+
+    private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+        if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
+            return CropBoundary.TOP;
+        }
+        if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
+            return CropBoundary.BOTTOM;
+        }
+        return CropBoundary.NONE;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 176a2c7..fd7db4b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -18,10 +18,12 @@
 
 import android.annotation.IdRes;
 import android.annotation.UiThread;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
@@ -168,19 +170,20 @@
     }
 
     private void edit() {
-        sendIntentWhenReady(Intent.ACTION_EDIT);
+        String editorPackage = mContext.getString(R.string.config_screenshotEditor);
+        sendIntentWhenReady(Intent.ACTION_EDIT, editorPackage);
     }
 
     private void share() {
-        sendIntentWhenReady(Intent.ACTION_SEND);
+        sendIntentWhenReady(Intent.ACTION_SEND, null);
     }
 
-    void sendIntentWhenReady(String action) {
+    void sendIntentWhenReady(String action, String component) {
         if (mExportFuture != null) {
             mExportFuture.addListener(() -> {
                 try {
                     ImageExporter.Result result = mExportFuture.get();
-                    sendIntent(action, result.uri);
+                    sendIntent(action, component, result.uri);
                     mCallback.onFinish();
                 } catch (InterruptedException | ExecutionException e) {
                     Log.e(TAG, "failed to export", e);
@@ -254,12 +257,16 @@
         }
     }
 
-    void sendIntent(String action, Uri uri) {
-        Intent editIntent = new Intent(action);
-        editIntent.setType("image/png");
-        editIntent.setData(uri);
-        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivityAsUser(editIntent, UserHandle.CURRENT);
+    void sendIntent(String action, String component, Uri uri) {
+        Intent intent = new Intent(action);
+        if (!TextUtils.isEmpty(component)) {
+            intent.setComponent(ComponentName.unflattenFromString(component));
+        }
+        intent.setType("image/png");
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index c1161f1..d707bca0 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -17,14 +17,19 @@
 package com.android.systemui.sensorprivacy
 
 import android.app.AppOpsManager
+import android.app.KeyguardManager
+import android.app.KeyguardManager.KeyguardDismissCallback
 import android.content.DialogInterface
 import android.content.Intent.EXTRA_PACKAGE_NAME
 import android.content.pm.PackageManager
 import android.content.res.Resources
 import android.hardware.SensorPrivacyManager
-import android.hardware.SensorPrivacyManager.*
+import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
 import android.os.Bundle
 import android.text.Html
+import android.util.Log
 import com.android.internal.app.AlertActivity
 import com.android.systemui.R
 
@@ -35,18 +40,29 @@
  * <p>The dialog is started for the user the app is running for which might be a secondary users.
  */
 class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListener {
+
+    companion object {
+        private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+    }
+
     private var sensor = -1
     private lateinit var sensorUsePackageName: String
 
     private lateinit var sensorPrivacyManager: SensorPrivacyManager
     private lateinit var appOpsManager: AppOpsManager
+    private lateinit var keyguardManager: KeyguardManager
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        setShowWhenLocked(true)
+
+        setFinishOnTouchOutside(false)
+
         setResult(RESULT_CANCELED)
         sensorPrivacyManager = getSystemService(SensorPrivacyManager::class.java)!!
         appOpsManager = getSystemService(AppOpsManager::class.java)!!
+        keyguardManager = getSystemService(KeyguardManager::class.java)!!
 
         sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return
         sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also {
@@ -107,8 +123,23 @@
     override fun onClick(dialog: DialogInterface?, which: Int) {
         when (which) {
             BUTTON_POSITIVE -> {
-                sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                setResult(RESULT_OK)
+                if (keyguardManager.isDeviceLocked) {
+                    keyguardManager
+                            .requestDismissKeyguard(this, object : KeyguardDismissCallback() {
+                        override fun onDismissError() {
+                            Log.e(LOG_TAG, "Cannot dismiss keyguard")
+                        }
+
+                        override fun onDismissSucceeded() {
+                            sensorPrivacyManager
+                                    .setIndividualSensorPrivacyForProfileGroup(sensor, false)
+                            setResult(RESULT_OK)
+                        }
+                    })
+                } else {
+                    sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+                    setResult(RESULT_OK)
+                }
             }
         }
 
@@ -120,4 +151,8 @@
 
         sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
     }
+
+    override fun onBackPressed() {
+        // do not allow backing out
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index c2e4e14..b7aa907 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -71,6 +71,8 @@
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
@@ -293,8 +295,8 @@
     @WMSingleton
     @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context) {
-        return new ShellTaskOrganizer(mainExecutor, context);
+            Context context, SizeCompatUI sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
     }
 
     @WMSingleton
@@ -380,8 +382,7 @@
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(
-            SyncTransactionQueue syncQueue) {
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
         return new FullscreenTaskListener(syncQueue);
     }
 
@@ -392,4 +393,12 @@
             @ShellAnimationThread ShellExecutor animExecutor) {
         return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
+            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
+        return SizeCompatUIController.create(context, displayController, imeController,
+                mainExecutor);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
deleted file mode 100644
index 71a0434..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-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;
-
-/**
- * runtest systemui -c com.android.systemui.SizeCompatModeActivityControllerTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
-    private static final int DISPLAY_ID = 0;
-
-    private SizeCompatModeActivityController mController;
-    private TaskStackChangeListener mTaskStackListener;
-    private @Mock TaskStackChangeListeners mMockTaskListeners;
-    private @Mock RestartActivityButton mMockButton;
-    private @Mock IBinder mMockActivityToken;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        doReturn(true).when(mMockButton).show();
-
-        mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
-                new CommandQueue(mContext)) {
-            @Override
-            RestartActivityButton createRestartButton(Context context) {
-                return mMockButton;
-            };
-        };
-
-        ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
-                ArgumentCaptor.forClass(TaskStackChangeListener.class);
-        verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
-        mTaskStackListener = listenerCaptor.getValue();
-    }
-
-    @Test
-    public void testOnSizeCompatModeActivityChanged() {
-        // Verifies that the restart button is added with non-null component name.
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
-        verify(mMockButton).show();
-        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
-
-        // Verifies that the restart button is removed with null component name.
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, null /* activityToken */);
-        verify(mMockButton).remove();
-    }
-
-    @Test
-    public void testChangeButtonVisibilityOnImeShowHide() {
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
-
-        // Verifies that the restart button is hidden when IME is visible.
-        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
-        mController.setImeWindowStatus(DISPLAY_ID, null /* token */, InputMethodService.IME_VISIBLE,
-                0 /* backDisposition */, false /* showImeSwitcher */);
-        verify(mMockButton).setVisibility(eq(View.GONE));
-
-        // Verifies that the restart button is visible when IME is hidden.
-        doReturn(View.GONE).when(mMockButton).getVisibility();
-        mController.setImeWindowStatus(DISPLAY_ID, null /* token */, 0 /* vis */,
-                0 /* backDisposition */, false /* showImeSwitcher */);
-        verify(mMockButton).setVisibility(eq(View.VISIBLE));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 72dd442..1e473cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -188,9 +188,8 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         event.recycle();
         // THEN illumination begins
-        verify(mUdfpsView).startIllumination();
         // AND onIlluminatedRunnable that notifies FingerprintManager is set
-        verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
         mOnIlluminatedRunnableCaptor.getValue().run();
         verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                 eq(0), eq(0f), eq(0f));
@@ -205,9 +204,8 @@
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
         // THEN illumination begins
-        verify(mUdfpsView).startIllumination();
         // AND onIlluminatedRunnable that notifies FingerprintManager is set
-        verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
         mOnIlluminatedRunnableCaptor.getValue().run();
         verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                 eq(0), eq(3f) /* minor */, eq(2f) /* major */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index 51cf501..47f4183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.view.View;
+import android.view.WindowInsetsController;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -87,6 +88,8 @@
     @Test
     public void testOnRotationProposalShowButtonShowNav() {
         // No navigation bar should not call to set visibility state
+        mRotationButtonController.onBehaviorChanged(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
         mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 false /* visible */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 4078d4d..79641db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -41,7 +40,6 @@
 import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,6 +49,12 @@
 
 import java.util.Optional;
 
+/**
+ * Tests for {@link WMShell}.
+ *
+ * Build/Install/Run:
+ *  atest SystemUITests:WMShellTest
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WMShellTest extends SysuiTestCase {
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index a11a745..663883a 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -21,7 +21,8 @@
 
 /**
  * Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
- * operations.
+ * operations; particularly display-state.
+ *
  * @hide
  */
 public class DisplayGroup {
@@ -33,17 +34,44 @@
         mGroupId = groupId;
     }
 
+    /** Returns the identifier for the Group. */
     int getGroupId() {
         return mGroupId;
     }
 
-    void addDisplay(LogicalDisplay display) {
+    /**
+     * Adds the provided {@code display} to the Group
+     *
+     * @param display the {@link LogicalDisplay} to add to the Group
+     */
+    void addDisplayLocked(LogicalDisplay display) {
         if (!mDisplays.contains(display)) {
             mDisplays.add(display);
         }
     }
 
-    boolean removeDisplay(LogicalDisplay display) {
+    /**
+     * Removes the provided {@code display} from the Group.
+     *
+     * @param display The {@link LogicalDisplay} to remove from the Group.
+     * @return {@code true} if the {@code display} was removed; otherwise {@code false}
+     */
+    boolean removeDisplayLocked(LogicalDisplay display) {
         return mDisplays.remove(display);
     }
+
+    /** Returns {@code true} if there are no {@link LogicalDisplay LogicalDisplays} in the Group. */
+    boolean isEmptyLocked() {
+        return mDisplays.isEmpty();
+    }
+
+    /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
+    int getSizeLocked() {
+        return mDisplays.size();
+    }
+
+    /** Returns the ID of the {@link LogicalDisplay} at the provided {@code index}. */
+    int getIdLocked(int index) {
+        return mDisplays.get(index).getDisplayIdLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c3f8d8c..e0baee7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -57,6 +57,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
@@ -190,6 +191,7 @@
     private static final int MSG_UPDATE_VIEWPORT = 5;
     private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
     private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
+    private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
@@ -237,6 +239,10 @@
     private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
             new CopyOnWriteArrayList<DisplayTransactionListener>();
 
+    /** List of all display group listeners. */
+    private final CopyOnWriteArrayList<DisplayGroupListener> mDisplayGroupListeners =
+            new CopyOnWriteArrayList<>();
+
     /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
     private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
             new SparseArray<>();
@@ -1677,6 +1683,11 @@
         mHandler.sendMessage(msg);
     }
 
+    private void sendDisplayGroupEvent(int groupId, int event) {
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event);
+        mHandler.sendMessage(msg);
+    }
+
     private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
                 displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
@@ -1721,6 +1732,35 @@
         mTempCallbacks.clear();
     }
 
+    // Runs on Handler thread.
+    // Delivers display group event notifications to callbacks.
+    private void deliverDisplayGroupEvent(int groupId, int event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering display group event: groupId=" + groupId + ", event="
+                    + event);
+        }
+
+        switch (event) {
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupAdded(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupChanged(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupRemoved(groupId);
+                }
+                break;
+        }
+    }
+
     private IMediaProjectionManager getProjectionService() {
         if (mProjectionService == null) {
             IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
@@ -1943,6 +1983,11 @@
                     }
                     deliverDisplayEvent(msg.arg1, uids, msg.arg2);
                     break;
+
+                case MSG_DELIVER_DISPLAY_GROUP_EVENT:
+                    deliverDisplayGroupEvent(msg.arg1, msg.arg2);
+                    break;
+
             }
         }
     }
@@ -1974,6 +2019,11 @@
         }
 
         @Override
+        public void onDisplayGroupEventLocked(int groupId, int event) {
+            sendDisplayGroupEvent(groupId, event);
+        }
+
+        @Override
         public void onTraversalRequested() {
             synchronized (mSyncRoot) {
                 scheduleTraversalLocked(false);
@@ -2708,11 +2758,25 @@
         }
 
         @Override
-        public boolean requestPowerState(DisplayPowerRequest request,
+        public boolean requestPowerState(int groupId, DisplayPowerRequest request,
                 boolean waitForNegativeProximity) {
             synchronized (mSyncRoot) {
-                return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
-                        .requestPowerState(request, waitForNegativeProximity);
+                final DisplayGroup displayGroup = mLogicalDisplayMapper.getDisplayGroupLocked(
+                        groupId);
+                if (displayGroup == null) {
+                    return true;
+                }
+
+                final int size = displayGroup.getSizeLocked();
+                boolean ready = true;
+                for (int i = 0; i < size; i++) {
+                    final DisplayPowerController displayPowerController =
+                            mDisplayPowerControllers.get(displayGroup.getIdLocked(i));
+                    ready &= displayPowerController.requestPowerState(request,
+                            waitForNegativeProximity);
+                }
+
+                return ready;
             }
         }
 
@@ -2732,6 +2796,16 @@
         }
 
         @Override
+        public void registerDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.remove(listener);
+        }
+
+        @Override
         public SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId) {
             return systemScreenshotInternal(displayId);
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e738878..8ee01be 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -55,6 +55,10 @@
     public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
 
+    public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
+    public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
+    public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
+
     /**
      * Temporary display info, used for comparing display configurations.
      */
@@ -99,7 +103,7 @@
     private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
 
     /** A mapping from logical display id to display group. */
-    private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
+    private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
     private final Listener mListener;
@@ -183,7 +187,7 @@
     }
 
     public int getDisplayGroupIdLocked(int displayId) {
-        final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+        final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
         if (displayGroup != null) {
             return displayGroup.getGroupId();
         }
@@ -191,6 +195,18 @@
         return -1;
     }
 
+    public DisplayGroup getDisplayGroupLocked(int groupId) {
+        final int size = mDisplayIdToGroupMap.size();
+        for (int i = 0; i < size; i++) {
+            final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
+            if (displayGroup.getGroupId() == groupId) {
+                return displayGroup;
+            }
+        }
+
+        return null;
+    }
+
     public void dumpLocked(PrintWriter pw) {
         pw.println("LogicalDisplayMapper:");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
@@ -320,7 +336,7 @@
             final int groupId = assignDisplayGroupIdLocked(isDefault);
             displayGroup = new DisplayGroup(groupId);
         } else {
-            displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
+            displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
         }
 
         LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
@@ -335,11 +351,23 @@
 
         mLogicalDisplays.put(displayId, display);
 
-        displayGroup.addDisplay(display);
-        mDisplayGroups.append(displayId, displayGroup);
+        displayGroup.addDisplayLocked(display);
+        mDisplayIdToGroupMap.append(displayId, displayGroup);
+
+        if (addNewDisplayGroup) {
+            // Group added events happen before Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+        }
 
         mListener.onLogicalDisplayEventLocked(display,
                 LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
+
+        if (!addNewDisplayGroup) {
+            // Group changed events happen after Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+        }
     }
 
     /**
@@ -356,33 +384,47 @@
             DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
                     display.getFrameRateOverrides();
             display.updateLocked(mDisplayDeviceRepo);
+            final DisplayGroup changedDisplayGroup;
             if (!display.isValidLocked()) {
                 mLogicalDisplays.removeAt(i);
-                mDisplayGroups.removeReturnOld(displayId).removeDisplay(display);
+                final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
+                displayGroup.removeDisplayLocked(display);
 
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
+
+                changedDisplayGroup = displayGroup;
             } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
                 final int flags = display.getDisplayInfoLocked().flags;
-                final DisplayGroup defaultDisplayGroup = mDisplayGroups.get(
+                final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
                         Display.DEFAULT_DISPLAY);
                 if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
                     // The display should have its own DisplayGroup.
-                    if (defaultDisplayGroup.removeDisplay(display)) {
+                    if (defaultDisplayGroup.removeDisplayLocked(display)) {
                         final int groupId = assignDisplayGroupIdLocked(false);
                         final DisplayGroup displayGroup = new DisplayGroup(groupId);
-                        displayGroup.addDisplay(display);
-                        mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
+                        displayGroup.addDisplayLocked(display);
                         display.updateDisplayGroupIdLocked(groupId);
+                        mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
+                        mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+                        changedDisplayGroup = defaultDisplayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 } else {
                     // The display should be a part of the default DisplayGroup.
-                    final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+                    final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
                     if (displayGroup != defaultDisplayGroup) {
-                        displayGroup.removeDisplay(display);
-                        defaultDisplayGroup.addDisplay(display);
-                        mDisplayGroups.put(displayId, defaultDisplayGroup);
+                        displayGroup.removeDisplayLocked(display);
+                        defaultDisplayGroup.addDisplayLocked(display);
                         display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId());
+                        mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+                        mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
+                        changedDisplayGroup = displayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 }
 
@@ -394,6 +436,7 @@
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+                changedDisplayGroup = null;
             } else {
                 // While applications shouldn't know nor care about the non-overridden info, we
                 // still need to let WindowManager know so it can update its own internal state for
@@ -403,6 +446,15 @@
                     mListener.onLogicalDisplayEventLocked(display,
                             LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
                 }
+                changedDisplayGroup = null;
+            }
+
+            // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
+            if (changedDisplayGroup != null) {
+                final int event = changedDisplayGroup.isEmptyLocked()
+                        ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
+                        : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
+                mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
             }
         }
     }
@@ -438,6 +490,7 @@
 
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
+        void onDisplayGroupEventLocked(int groupId, int event);
         void onTraversalRequested();
     }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index d2111e7..2029f39 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -169,8 +169,9 @@
             sb.append(c++);
             sb.append("]: lang=\"");
             sb.append(family.getLocaleList().toLanguageTags());
+            sb.append("\"");
             if (family.getVariant() != FontConfig.FontFamily.VARIANT_DEFAULT) {
-                sb.append("\", variant=");
+                sb.append(", variant=");
                 switch (family.getVariant()) {
                     case FontConfig.FontFamily.VARIANT_COMPACT:
                         sb.append("Compact");
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 61c8b17..461d519 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1419,8 +1419,10 @@
                             conversation.setPkg(p.pkg);
                             conversation.setUid(p.uid);
                             conversation.setNotificationChannel(nc);
-                            conversation.setParentChannelLabel(
-                                    p.channels.get(nc.getParentChannelId()).getName());
+                            NotificationChannel parent = p.channels.get(nc.getParentChannelId());
+                            conversation.setParentChannelLabel(parent == null
+                                    ? null
+                                    : parent.getName());
                             boolean blockedByGroup = false;
                             if (nc.getGroup() != null) {
                                 NotificationChannelGroup group = p.groups.get(nc.getGroup());
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3207d56a..212edf6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -122,6 +122,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collection;
@@ -155,6 +156,8 @@
     boolean mComponents;
     int mQueryFlags;
 
+    private static final SecureRandom RANDOM = new SecureRandom();
+
     PackageManagerShellCommand(PackageManagerService service, Context context) {
         mInterface = service;
         mLegacyPermissionManager = LocalServices.getService(LegacyPermissionManagerInternal.class);
@@ -3146,7 +3149,7 @@
 
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
-                final String name = "base." + (isApex ? "apex" : "apk");
+                final String name = "base" + RANDOM.nextInt() + "." + (isApex ? "apex" : "apk");
                 final Metadata metadata = Metadata.forStdIn(name);
                 session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
                         metadata.toByteArray(), null);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2929568..349d556 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4881,7 +4881,7 @@
         }
 
         if (ps.sharedUser == null || permissionNames != null || dumpAll) {
-            dumpInstallPermissionsLPr(pw, prefix + "  ", permissionNames, permissionsState);
+            dumpInstallPermissionsLPr(pw, prefix + "  ", permissionNames, permissionsState, users);
         }
 
         if (dumpAllComponents) {
@@ -5131,9 +5131,12 @@
                     continue;
                 }
 
-                dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState);
+                List<UserInfo> users = getAllUsers(UserManagerService.getInstance());
 
-                for (int userId : UserManagerService.getInstance().getUserIds()) {
+                dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState, users);
+
+                for (UserInfo user : users) {
+                    final int userId = user.id;
                     final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid(
                             userId, su.userId));
                     final Collection<PermissionState> permissions =
@@ -5247,31 +5250,55 @@
         }
     }
 
-    void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames,
-            LegacyPermissionState permissionsState) {
-        Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
-                UserHandle.USER_SYSTEM);
-        boolean hasInstallPermissions = false;
-        for (PermissionState permissionState : permissionStates) {
-            if (!permissionState.isRuntime()) {
-                hasInstallPermissions = true;
-                break;
-            }
-        }
-        if (hasInstallPermissions) {
-            pw.print(prefix); pw.println("install permissions:");
+    void dumpInstallPermissionsLPr(PrintWriter pw, String prefix,
+            ArraySet<String> filterPermissionNames, LegacyPermissionState permissionsState,
+            List<UserInfo> users) {
+        ArraySet<String> dumpPermissionNames = new ArraySet<>();
+        for (UserInfo user : users) {
+            int userId = user.id;
+            Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
+                    userId);
             for (PermissionState permissionState : permissionStates) {
                 if (permissionState.isRuntime()) {
                     continue;
                 }
-                if (permissionNames != null
-                        && !permissionNames.contains(permissionState.getName())) {
+                String permissionName = permissionState.getName();
+                if (filterPermissionNames != null
+                        && !filterPermissionNames.contains(permissionName)) {
                     continue;
                 }
-                pw.print(prefix); pw.print("  "); pw.print(permissionState.getName());
-                    pw.print(": granted="); pw.print(permissionState.isGranted());
-                    pw.println(permissionFlagsToString(", flags=",
-                        permissionState.getFlags()));
+                dumpPermissionNames.add(permissionName);
+            }
+        }
+        boolean printedSomething = false;
+        for (String permissionName : dumpPermissionNames) {
+            PermissionState systemPermissionState = permissionsState.getPermissionState(
+                    permissionName, UserHandle.USER_SYSTEM);
+            for (UserInfo user : users) {
+                int userId = user.id;
+                PermissionState permissionState;
+                if (userId == UserHandle.USER_SYSTEM) {
+                    permissionState = systemPermissionState;
+                } else {
+                    permissionState = permissionsState.getPermissionState(permissionName, userId);
+                    if (Objects.equals(permissionState, systemPermissionState)) {
+                        continue;
+                    }
+                }
+                if (!printedSomething) {
+                    pw.print(prefix); pw.println("install permissions:");
+                    printedSomething = true;
+                }
+                pw.print(prefix); pw.print("  "); pw.print(permissionName);
+                pw.print(": granted="); pw.print(
+                        permissionState != null && permissionState.isGranted());
+                pw.print(permissionFlagsToString(", flags=",
+                        permissionState != null ? permissionState.getFlags() : 0));
+                if (userId == UserHandle.USER_SYSTEM) {
+                    pw.println();
+                } else {
+                    pw.print(", userId="); pw.println(userId);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 851ddd1..e8be9b6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -19,6 +19,7 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
@@ -33,6 +34,7 @@
 import android.os.Build;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.permission.PermissionManager;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 
@@ -42,6 +44,7 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
+import java.util.List;
 
 /**
  * The v2 of {@link PackageParser} for use when parsing is initiated in the server and must
@@ -118,10 +121,15 @@
             displayMetrics.setToDefaults();
         }
 
+        PermissionManager permissionManager = ActivityThread.currentApplication()
+                .getSystemService(PermissionManager.class);
+        List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
+                .getSplitPermissions();
+
         mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
 
         parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics,
-                callback);
+                splitPermissions, callback);
 
         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
             ApplicationInfo appInfo = mSharedAppInfo.get();
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
index 92f22a4..e8eae47 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
@@ -104,6 +104,26 @@
     }
 
     /**
+     * Get the permission state for a permission and a user.
+     *
+     * @param permissionName the permission name
+     * @param userId the user ID
+     * @return the permission state
+     *
+     * @hide
+     */
+    @Nullable
+    public PermissionState getPermissionState(@NonNull String permissionName,
+            @UserIdInt int userId) {
+        checkUserId(userId);
+        UserState userState = mUserStates.get(userId);
+        if (userState == null) {
+            return null;
+        }
+        return userState.getPermissionState(permissionName);
+    }
+
+    /**
      * Put a permission state for a user.
      *
      * @param permissionState the permission state
@@ -142,10 +162,10 @@
     }
 
     /**
-     * Get all the runtime permission states for a user.
+     * Get all the permission states for a user.
      *
      * @param userId the user ID
-     * @return the runtime permission states
+     * @return the permission states
      */
     @NonNull
     public Collection<PermissionState> getPermissionStates(@UserIdInt int userId) {
@@ -301,5 +321,25 @@
         public int getFlags() {
             return mFlags;
         }
+
+        @Override
+        public boolean equals(@Nullable Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object == null || getClass() != object.getClass()) {
+                return false;
+            }
+            PermissionState that = (PermissionState) object;
+            return mRuntime == that.mRuntime
+                    && mGranted == that.mGranted
+                    && mFlags == that.mFlags
+                    && Objects.equals(mName, that.mName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mName, mRuntime, mGranted, mFlags);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index db4b6d0..8e0d632 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2892,8 +2892,8 @@
                         PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
 
-            mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
-                    mRequestWaitForNegativeProximity);
+            mDisplayReady = mDisplayManagerInternal.requestPowerState(Display.DEFAULT_DISPLAY_GROUP,
+                    displayPowerRequest, mRequestWaitForNegativeProximity);
             mRequestWaitForNegativeProximity = false;
 
             if (DEBUG_SPEW) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d5d06f9..35260d3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5500,12 +5500,9 @@
             // The callback is only interested in the foreground changes of fullscreen activity.
             return;
         }
+        // TODO(b/178327644) Update for per Task size compat
         if (!r.inSizeCompatMode()) {
             if (mLastCompatModeActivity != null) {
-                // TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
-                mAtmService.getTaskChangeNotificationController()
-                        .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
-                // This will do nothing until SizeCompatModeActivityController is moved to shell
                 organizedTask.onSizeCompatActivityChanged();
             }
             mLastCompatModeActivity = null;
@@ -5515,10 +5512,6 @@
             return;
         }
         mLastCompatModeActivity = r;
-        // TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
-        mAtmService.getTaskChangeNotificationController()
-                .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
-        // This will do nothing until SizeCompatModeActivityController is moved to shell
         organizedTask.onSizeCompatActivityChanged();
     }
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3d3e31d..8d8bdcb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -22,7 +22,6 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
-import static android.Manifest.permission.USE_BACKGROUND_BLUR;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -111,7 +110,6 @@
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanAcquireSleepToken;
-    final boolean mCanUseBackgroundBlur;
     private AlertWindowNotification mAlertWindowNotification;
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
@@ -142,8 +140,6 @@
                 && !mService.mAtmInternal.isCallerRecents(mUid);
         mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
                 == PERMISSION_GRANTED;
-        mCanUseBackgroundBlur = service.mContext.checkCallingOrSelfPermission(USE_BACKGROUND_BLUR)
-                == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 931f529..1cc6239 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1652,7 +1652,7 @@
 
             final WindowState win = new WindowState(this, session, client, token, parentWindow,
                     appOp[0], attrs, viewVisibility, session.mUid, userId,
-                    session.mCanAddInternalSystemWindow, session.mCanUseBackgroundBlur);
+                    session.mCanAddInternalSystemWindow);
             if (win.mDeathRecipient == null) {
                 // Client has apparently died, so there is no reason to
                 // continue.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0250376..c698591 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -297,8 +296,6 @@
     final int mShowUserId;
     /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
     final boolean mOwnerCanAddInternalSystemWindow;
-    /** The owner has {@link android.Manifest.permission#USE_BACKGROUND_BLUR} */
-    final boolean mOwnerCanUseBackgroundBlur;
     final WindowId mWindowId;
     WindowToken mToken;
     // The same object as mToken if this is an app window and null for non-app windows.
@@ -897,11 +894,9 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
-            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            boolean ownerCanUseBackgroundBlur) {
+            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
         this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
-                ownerCanAddInternalSystemWindow, ownerCanUseBackgroundBlur,
-                new PowerManagerWrapper() {
+                ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
                     @Override
                     public void wakeUp(long time, @WakeReason int reason, String details) {
                         service.mPowerManager.wakeUp(time, reason, details);
@@ -917,7 +912,7 @@
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
             int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            boolean ownerCanUseBackgroundBlur, PowerManagerWrapper powerManagerWrapper) {
+            PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mTmpTransaction = service.mTransactionFactory.get();
         mSession = s;
@@ -928,7 +923,6 @@
         mOwnerUid = ownerId;
         mShowUserId = showUserId;
         mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
-        mOwnerCanUseBackgroundBlur = ownerCanUseBackgroundBlur;
         mWindowId = new WindowId(this);
         mAttrs.copyFrom(a);
         mLastSurfaceInsets.set(mAttrs.surfaceInsets);
@@ -5275,7 +5269,7 @@
         if (!mAnimatingExit && mAppDied) {
             mIsDimming = true;
             getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
-        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || isBlurEnabled())
+        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || mAttrs.backgroundBlurRadius != 0)
                    && isVisibleNow() && !mHidden) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or background blur is requested
@@ -5284,15 +5278,11 @@
             // 4. The WS is not hidden.
             mIsDimming = true;
             final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
-            final int blurRadius = isBlurEnabled() ? mAttrs.backgroundBlurRadius : 0;
-            getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius);
+            getDimmer().dimBelow(
+                    getSyncTransaction(), this, mAttrs.dimAmount, mAttrs.backgroundBlurRadius);
         }
     }
 
-    private boolean isBlurEnabled() {
-        return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur;
-    }
-
     /**
      * Notifies SF about the priority of the window, if it changed. SF then uses this information
      * to decide which window's desired rendering rate should have a priority when deciding about
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 156ef79..31cc295 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -123,7 +123,7 @@
     }
     return -1;
 }
-static bool getAnyPageAdvice(const Vma& vma) {
+static int getAnyPageAdvice(const Vma& vma) {
     if (vma.inode == 0 && !vma.is_shared) {
         return MADV_PAGEOUT;
     }
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 7b379e5..5a5b0a8 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -22,6 +22,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <core_jni_helpers.h>
+#include <cutils/multiuser.h>
 #include <cutils/trace.h>
 #include <endian.h>
 #include <nativehelper/JNIHelp.h>
@@ -375,6 +376,9 @@
     }
 
 private:
+    // Bitmask of supported features.
+    DataLoaderFeatures getFeatures() const final { return DATA_LOADER_FEATURE_UID; }
+
     // Lifecycle.
     bool onCreate(const android::dataloader::DataLoaderParams& params,
                   android::dataloader::FilesystemConnectorPtr ifs,
@@ -554,51 +558,6 @@
         return true;
     }
 
-    // Read tracing.
-    struct TracedRead {
-        uint64_t timestampUs;
-        android::dataloader::FileId fileId;
-        uint32_t firstBlockIdx;
-        uint32_t count;
-    };
-
-    void onPageReads(android::dataloader::PageReads pageReads) final {
-        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
-        if (CC_LIKELY(!trace)) {
-            return;
-        }
-
-        TracedRead last = {};
-        for (auto&& read : pageReads) {
-            if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
-                traceRead(last);
-                last = TracedRead{
-                        .timestampUs = read.bootClockTsUs,
-                        .fileId = read.id,
-                        .firstBlockIdx = (uint32_t)read.block,
-                        .count = 1,
-                };
-            } else {
-                ++last.count;
-            }
-        }
-        traceRead(last);
-    }
-
-    void traceRead(const TracedRead& read) {
-        if (!read.count) {
-            return;
-        }
-
-        FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
-        auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
-                                               static_cast<long long>(read.firstBlockIdx),
-                                               static_cast<long long>(read.count),
-                                               static_cast<int>(fileIdx));
-        ATRACE_BEGIN(str.c_str());
-        ATRACE_END();
-    }
-
     // Streaming.
     bool initStreaming(unique_fd inout, MetadataMode mode) {
         mEventFd.reset(eventfd(0, EFD_CLOEXEC));
@@ -634,7 +593,10 @@
     }
 
     // IFS callbacks.
-    void onPendingReads(dataloader::PendingReads pendingReads) final {
+    void onPendingReads(dataloader::PendingReads pendingReads) final {}
+    void onPageReads(dataloader::PageReads pageReads) final {}
+
+    void onPendingReadsWithUid(dataloader::PendingReadsWithUid pendingReads) final {
         std::lock_guard lock{mOutFdLock};
         if (mOutFd < 0) {
             return;
@@ -660,6 +622,67 @@
         }
     }
 
+    // Read tracing.
+    struct TracedRead {
+        uint64_t timestampUs;
+        android::dataloader::FileId fileId;
+        android::dataloader::Uid uid;
+        uint32_t firstBlockIdx;
+        uint32_t count;
+    };
+
+    void onPageReadsWithUid(dataloader::PageReadsWithUid pageReads) final {
+        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+        if (CC_LIKELY(!trace)) {
+            return;
+        }
+
+        TracedRead last = {};
+        for (auto&& read : pageReads) {
+            if (read.id != last.fileId || read.uid != last.uid ||
+                read.block != last.firstBlockIdx + last.count) {
+                traceRead(last);
+                last = TracedRead{
+                        .timestampUs = read.bootClockTsUs,
+                        .fileId = read.id,
+                        .uid = read.uid,
+                        .firstBlockIdx = (uint32_t)read.block,
+                        .count = 1,
+                };
+            } else {
+                ++last.count;
+            }
+        }
+        traceRead(last);
+    }
+
+    void traceRead(const TracedRead& read) {
+        if (!read.count) {
+            return;
+        }
+
+        FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
+
+        std::string trace;
+        if (read.uid != kIncFsNoUid) {
+            auto appId = multiuser_get_app_id(read.uid);
+            auto userId = multiuser_get_user_id(read.uid);
+            trace = android::base::
+                    StringPrintf("page_read: index=%lld count=%lld file=%d appid=%d userid=%d",
+                                 static_cast<long long>(read.firstBlockIdx),
+                                 static_cast<long long>(read.count), static_cast<int>(fileIdx),
+                                 static_cast<int>(appId), static_cast<int>(userId));
+        } else {
+            trace = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
+                                                static_cast<long long>(read.firstBlockIdx),
+                                                static_cast<long long>(read.count),
+                                                static_cast<int>(fileIdx));
+        }
+
+        ATRACE_BEGIN(trace.c_str());
+        ATRACE_END();
+    }
+
     void receiver(unique_fd inout, MetadataMode mode) {
         std::vector<uint8_t> data;
         std::vector<IncFsDataBlock> instructions;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 132f973..56cb3d1 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1091,7 +1091,7 @@
         maxPendingTimeUs = std::max(maxPendingTimeUs, microseconds(timeouts.maxPendingTimeUs));
     }
     if (maxPendingTimeUs < Constants::minPerUidTimeout) {
-        LOG(ERROR) << "Skip setting timeouts: maxPendingTime < Constants::minPerUidTimeout"
+        LOG(ERROR) << "Skip setting  read timeouts (maxPendingTime < Constants::minPerUidTimeout): "
                    << duration_cast<milliseconds>(maxPendingTimeUs).count() << "ms < "
                    << Constants::minPerUidTimeout.count() << "ms";
         return;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3573177..25d3f77 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -221,10 +221,6 @@
             timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
         }
 
-        LOG(ERROR) << "Set read timeouts: " << timeouts.size() << " ["
-                   << (timeouts.empty() ? -1 : timeouts.front().uid) << "@"
-                   << (timeouts.empty() ? -1 : timeouts.front().minTimeUs / 1000) << "ms]";
-
         return incfs::setUidReadTimeouts(control, timeouts);
     }
 };
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 8d744c4..9b6c723 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
@@ -700,14 +700,14 @@
     public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder()
-                        .addSchemaType("FakeType")
+                        .addFilterSchemas("FakeType")
                         .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
                         .build();
         mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
 
         searchSpec =
                 new SearchSpec.Builder()
-                        .addNamespace("FakeNamespace")
+                        .addFilterNamespaces("FakeNamespace")
                         .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
                         .build();
         mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index bb223b3..fb13d87 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -55,6 +55,7 @@
 
 import java.io.File;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.function.Function;
 
 /**
@@ -523,7 +524,7 @@
         int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
 
         ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
-                flags, false /*collectCertificates*/);
+                flags, Collections.emptyList(), false /*collectCertificates*/);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index d8910de..4dc9a90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -19,12 +19,10 @@
 import android.annotation.RawRes
 import android.content.Context
 import android.content.pm.parsing.ParsingPackage
-import android.content.pm.parsing.ParsingPackageImpl
 import android.content.pm.parsing.ParsingPackageUtils
 import android.content.pm.parsing.result.ParseInput
 import android.content.pm.parsing.result.ParseInput.DeferredError
 import android.content.pm.parsing.result.ParseResult
-import android.content.res.TypedArray
 import android.os.Build
 import androidx.test.InstrumentationRegistry
 import com.android.frameworks.servicestests.R
@@ -130,7 +128,7 @@
                 input.copyTo(output)
             }
         }
-        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/,
+        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
                 false /*collectCertificates*/)
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 1d0b595..592eea1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -179,7 +179,8 @@
                 .thenReturn(mPowerSaveState);
         when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
-        when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
+        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
+                .thenReturn(true);
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8c744c9..fc1cb70 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3536,6 +3536,28 @@
         assertFalse(conversationWrapperContainsChannel(convos, channel2));
     }
 
+    @Test
+    public void testGetConversations_parentDeleted() {
+        String convoId = "convo";
+        NotificationChannel messages =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
+        channel.setConversationId(messages.getId(), convoId);
+        channel.setImportantConversation(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages");
+
+        List<ConversationChannelWrapper> convos =
+                mHelper.getConversations(IntArray.wrap(new int[] {0}), true);
+
+        assertEquals(1, convos.size());
+        assertTrue(conversationWrapperContainsChannel(convos, channel));
+    }
+
     private boolean conversationWrapperContainsChannel(List<ConversationChannelWrapper> list,
             NotificationChannel expected) {
         for (ConversationChannelWrapper ccw : list) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 91fd7a2..89b962b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -541,7 +541,7 @@
         return new WindowState(mWm, mock(Session.class), new TestIWindow(), token,
                 null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
                 View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
-                false /* ownerCanAddInternalSystemWindow */, false /* ownerCanUseBackgroundBlur */);
+                false /* ownerCanAddInternalSystemWindow */);
     }
 
     private WindowToken createWindowToken(int type) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index bbd89b8..cc4d4ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -52,14 +52,12 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.TaskStackListener;
 import android.app.WindowConfiguration;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 
@@ -72,8 +70,6 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-
 /**
  * Tests for Size Compatibility mode.
  *
@@ -479,52 +475,6 @@
     }
 
     /**
-     * Ensures that {@link TaskStackListener} can receive callback about the activity in size
-     * compatibility mode.
-     *
-     * TODO(b/178327644) Remove after update DC#handleActivitySizeCompatModeIfNeeded
-     */
-    @Test
-    public void testHandleActivitySizeCompatMode() {
-        setUpDisplaySizeWithApp(1000, 2000);
-        doReturn(true).when(mTask).isOrganized();
-        ActivityRecord activity = mActivity;
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
-        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
-        assertFitted();
-
-        final ArrayList<IBinder> compatTokens = new ArrayList<>();
-        mAtm.getTaskChangeNotificationController().registerTaskStackListener(
-                new TaskStackListener() {
-                    @Override
-                    public void onSizeCompatModeActivityChanged(int displayId,
-                            IBinder activityToken) {
-                        compatTokens.add(activityToken);
-                    }
-                });
-
-        // Resize the display so that the activity exercises size-compat mode.
-        resizeDisplay(mTask.mDisplayContent, 1000, 2500);
-
-        // Expect the exact token when the activity is in size compatibility mode.
-        assertEquals(1, compatTokens.size());
-        assertEquals(activity.appToken, compatTokens.get(0));
-
-        compatTokens.clear();
-        // Make the activity resizable again by restarting it
-        activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        activity.mVisibleRequested = true;
-        activity.restartProcessIfVisible();
-        // The full lifecycle isn't hooked up so manually set state to resumed
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
-        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
-
-        // Expect null token when switching to non-size-compat mode activity.
-        assertEquals(1, compatTokens.size());
-        assertEquals(null, compatTokens.get(0));
-    }
-
-    /**
      * Ensures that {@link TaskOrganizerController} can receive callback about the activity in size
      * compatibility mode.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 8b604a3..3492d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -340,7 +340,7 @@
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
                 OP_NONE, attrs, VISIBLE, ownerId, userId,
-                ownerCanAddInternalSystemWindow, false /* ownerCanUseBackgroundBlur */,
+                ownerCanAddInternalSystemWindow,
                 powerManagerWrapper);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
@@ -1213,8 +1213,7 @@
         TestWindowState(WindowManagerService service, Session session, IWindow window,
                 WindowManager.LayoutParams attrs, WindowToken token) {
             super(service, session, window, token, null, OP_NONE, attrs, 0, 0, 0,
-                    false /* ownerCanAddInternalSystemWindow */,
-                    false /* ownerCanUseBackgroundBlur */);
+                    false /* ownerCanAddInternalSystemWindow */);
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c1fb74d..be36f82 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1782,7 +1782,7 @@
             }
 
             @Override
-            public void onPackageModified(String pkgName) {
+            public void onPackageModified(@NonNull String pkgName) {
                 // If the package modified is not in the current user, then don't bother making
                 // any changes as we are going to do any initialization needed when we switch users.
                 if (mCurUser != getChangingUserId()) {
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 02c75ed..39dc9c2 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,6 +16,7 @@
     name: "ApkVerityTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    static_libs: ["frameworks-base-hostutils"],
     test_suites: ["general-tests", "vts"],
     target_required: [
         "block_device_writer_module",
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 629b6c7..d0eb9be 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -21,10 +21,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.fsverity.AddFsVerityCertRule;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -35,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -85,40 +86,25 @@
     private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
     private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
 
-    private static final String APK_VERITY_STANDARD_MODE = "2";
-
     /** Only 4K page is supported by fs-verity currently. */
     private static final int FSVERITY_PAGE_SIZE = 4096;
 
+    @Rule
+    public final AddFsVerityCertRule mAddFsVerityCertRule =
+            new AddFsVerityCertRule(this, CERT_PATH);
+
     private ITestDevice mDevice;
-    private String mKeyId;
 
     @Before
     public void setUp() throws DeviceNotAvailableException {
         mDevice = getDevice();
 
-        String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
-        assumeTrue(mDevice.getLaunchApiLevel() >= 30
-                || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
-
-        mKeyId = expectRemoteCommandToSucceed(
-                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
-        if (!mKeyId.matches("^\\d+$")) {
-            String keyId = mKeyId;
-            mKeyId = null;
-            fail("Key ID is not decimal: " + keyId);
-        }
-
         uninstallPackage(TARGET_PACKAGE);
     }
 
     @After
     public void tearDown() throws DeviceNotAvailableException {
         uninstallPackage(TARGET_PACKAGE);
-
-        if (mKeyId != null) {
-            expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
-        }
     }
 
     @Test
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
new file mode 100644
index 0000000..d809fe8
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+java_test_host {
+    name: "UpdatableSystemFontTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    static_libs: ["frameworks-base-hostutils"],
+    test_suites: ["general-tests", "vts"],
+    data: [
+        ":NotoColorEmojiTtf",
+        ":UpdatableSystemFontTestCertDer",
+        ":UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+        ":UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+        ":UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+        ":UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+        ":UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+    ],
+}
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
new file mode 100644
index 0000000..efe5d70
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Updatable system font integration/regression test">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- This test requires root to side load fs-verity cert. -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
+        <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="UpdatableSystemFontTest.jar" />
+    </test>
+</configuration>
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
new file mode 100644
index 0000000..6d161a5
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.updatablesystemfont;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests if fonts can be updated by 'cmd font'.
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+
+    private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
+
+    private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
+    private static final String NOTO_COLOR_EMOJI_TTF = "NotoColorEmoji.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig";
+    private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig";
+    private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF =
+            "/data/local/tmp/NotoColorEmoji.ttf";
+    private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig";
+
+    @Rule
+    public final AddFsVerityCertRule mAddFsverityCertRule =
+            new AddFsVerityCertRule(this, CERT_PATH);
+
+    @Before
+    public void setUp() throws Exception {
+        expectRemoteCommandToSucceed("cmd font clear");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        expectRemoteCommandToSucceed("cmd font clear");
+    }
+
+    @Test
+    public void updateFont() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+    }
+
+    @Test
+    public void updateFont_twice() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+        String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath2).startsWith("/data/fonts/files/");
+        assertThat(fontPath2).isNotEqualTo(fontPath);
+    }
+
+    @Test
+    public void updatedFont_dataFileIsImmutableAndReadable() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data");
+
+        expectRemoteCommandToFail("echo -n '' >> " + fontPath);
+        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+    }
+
+    @Test
+    public void updateFont_invalidCert() throws Exception {
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+    }
+
+    @Test
+    public void updateFont_downgradeFromSystem() throws Exception {
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG));
+    }
+
+    @Test
+    public void updateFont_downgradeFromData() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+    }
+
+    private String getFontPath(String fontFileName) throws Exception {
+        // TODO: add a dedicated command for testing.
+        String lines = expectRemoteCommandToSucceed("cmd font dump");
+        for (String line : lines.split("\n")) {
+            Matcher m = PATTERN_FONT.matcher(line);
+            if (m.find() && m.group(1).endsWith(fontFileName)) {
+                return m.group(1);
+            }
+        }
+        CLog.e("Font not found: " + fontFileName);
+        return null;
+    }
+
+    private String expectRemoteCommandToSucceed(String cmd) throws Exception {
+        CommandResult result = getDevice().executeShellV2Command(cmd);
+        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+                .that(result.getStatus())
+                .isEqualTo(CommandStatus.SUCCESS);
+        return result.getStdout();
+    }
+
+    private void expectRemoteCommandToFail(String cmd) throws Exception {
+        CommandResult result = getDevice().executeShellV2Command(cmd);
+        assertWithMessage("Unexpected success from `" + cmd + "`: " + result.getStderr())
+                .that(result.getStatus())
+                .isNotEqualTo(CommandStatus.SUCCESS);
+    }
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
new file mode 100644
index 0000000..1296699
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -0,0 +1,82 @@
+// 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.
+
+filegroup {
+    name: "UpdatableSystemFontTestKeyPem",
+    srcs: ["UpdatableSystemFontTestKey.pem"],
+}
+
+filegroup {
+    name: "UpdatableSystemFontTestCertPem",
+    srcs: ["UpdatableSystemFontTestCert.pem"],
+}
+
+filegroup {
+    name: "UpdatableSystemFontTestCertDer",
+    srcs: ["UpdatableSystemFontTestCert.der"],
+}
+
+genrule_defaults {
+    name: "updatable_system_font_increment_font_revision_default",
+    tools: ["update_font_metadata"],
+    cmd: "$(location update_font_metadata) " +
+        "--input=$(in) " +
+        "--output=$(out) " +
+        "--revision=+1",
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+    defaults: ["updatable_system_font_increment_font_revision_default"],
+    srcs: [":NotoColorEmojiTtf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+    defaults: ["updatable_system_font_increment_font_revision_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf"],
+}
+
+genrule_defaults {
+    name: "updatable_system_font_sig_gen_default",
+    tools: ["fsverity"],
+    tool_files: [":UpdatableSystemFontTestKeyPem", ":UpdatableSystemFontTestCertPem"],
+    cmd: "$(location fsverity) sign $(in) $(out) " +
+        "--key=$(location :UpdatableSystemFontTestKeyPem) " +
+        "--cert=$(location :UpdatableSystemFontTestCertPem) " +
+        "> /dev/null",
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":NotoColorEmojiTtf"],
+    out: ["UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV2Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig"],
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
new file mode 100644
index 0000000..f7aa15f
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
new file mode 100644
index 0000000..0cd1f66
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFOTCCAyGgAwIBAgIUFaI1D5NtwkCVM3G4bFZ6sQSb598wDQYJKoZIhvcNAQEL
+BQAwLDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lk
+MB4XDTIxMDIwMTA3MzAyNFoXDTIxMDMwMzA3MzAyNFowLDELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lkMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0U1zptc41E65ooeBPD33Mjgp6cYPydyj2Acq80Xy7lP7
+d6/2t6w7nNNl2x3n8dAhOl3de3IxTp6JI2SdoRb7obfcp+hWJoo/cxnHpr3q/u4R
+KED0rmWaOVHpGbajSTFZgN+cTbTKJbgtXm/H65x1QfO18ep/vj5fRiu1xPpJqDv/
+xuvuko2U3eC2+NayxzCWXVFrKPLx8GvzSQ3Utaug17vs7/5GqkRJgq3lk4DvmjNA
+vY8YA4RAkII1sSaceAWFEG6ztENLu2kjcxAI9qHxxBwQZit/NtFVlFGqSN3MEYjS
+M9Fz04RsUxF672QJpAgwCJDZ41rdB3hkHvOUK9PcepBsHdZq9cQ+E64+TX+jsJLu
+VouViKlYr6WYjvhfqZeRhwbj7CoEZ2DyEZKrl27fgWaidUT5LGEQLVxg90ymbimI
+6UwXRUwmRBQJBdRO4RGvngtqxRuamyjAKDDHx5YccXCX4FWLUypyQlz0asojvbJZ
+Og7DFa1qsRdGrGIRoQJ8pYnAjBJfSudr1l0mR7fZSfZc0W9ZmuROWx9Ip7aJWQnQ
+8JLtbNPuFLD2qbmg9Y1lcXJp1FvI9FcM8JsBqZNEANQwwsdTaa8gw+3W6J2SXKQP
+H+yZI/fJWWWRFADtqmpxtvXK9K+Cy1HQmg7D0IIxVPp8rrbz6TGrg+R3/N9FBWMC
+AwEAAaNTMFEwHQYDVR0OBBYEFDS48o2UAstoOyLMcgamHKrZdl3cMB8GA1UdIwQY
+MBaAFDS48o2UAstoOyLMcgamHKrZdl3cMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggIBAF7taBYAe20tWZu0pY9d4Z8il4LoJcRrKF4YiA02UizErgCF
+h4iECy6+pcu7DJUfvCh3dCWE7CDG+OnfUWTwEHVG9n8XI/ydetBUG76PZwTadI7B
+gzJ1y7/vWqJo5U6ki+sXNmq3hkgNsNZgza3LpdovkWJYeRdffM6m/bimzwYx9id8
+5mKw2PcbVZcb25r+0dCoLVJsqqCoRjdYUy/MKPutWG2bPzmaIv8KsKFN+mzlwhJH
+lpJ/LR+3NoaHrOCFG7CW/2Ihe501vmdQ2m/VKosyk0igw8WmTsY6xMbw2t77yKkD
+hnJr1NbhKeEV9gAB2BFX8nRWI7NTgp8fG78YLVz1UcbIHmYLgFoc3ezyma+CoR86
+ER20lKd4+TNnz4RtaPdZlBa0Ba3bsMtEneqlrHvcPrZ5tgGsQR9+cy3ZtTZ/LUQX
++Xuj/EoJXuuB3hkhg52zawN5n7WUe8efWHcv1jHqeIj0phcgbZ6u4fFBPsYjzDKe
+VuYHXglNOchmoBQwEaJI/TCiEgI8dcSJXSquLAXrtznVnxzT46ZMEt5LaW1/1NLx
+q//yoPdolCI0lpunh5jvIZJpUl5XMjxVSyaveQDNVqJkITWzWqIxAT5yTLtkCNlW
+c1XyzeHkpMItiJtBruExmnaTmNjlVKsXP8wQFOYbDGgXY5iHIMbgovptRyH/
+-----END CERTIFICATE-----
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
new file mode 100644
index 0000000..09bb104
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDRTXOm1zjUTrmi
+h4E8PfcyOCnpxg/J3KPYByrzRfLuU/t3r/a3rDuc02XbHefx0CE6Xd17cjFOnokj
+ZJ2hFvuht9yn6FYmij9zGcemver+7hEoQPSuZZo5UekZtqNJMVmA35xNtMoluC1e
+b8frnHVB87Xx6n++Pl9GK7XE+kmoO//G6+6SjZTd4Lb41rLHMJZdUWso8vHwa/NJ
+DdS1q6DXu+zv/kaqREmCreWTgO+aM0C9jxgDhECQgjWxJpx4BYUQbrO0Q0u7aSNz
+EAj2ofHEHBBmK3820VWUUapI3cwRiNIz0XPThGxTEXrvZAmkCDAIkNnjWt0HeGQe
+85Qr09x6kGwd1mr1xD4Trj5Nf6Owku5Wi5WIqVivpZiO+F+pl5GHBuPsKgRnYPIR
+kquXbt+BZqJ1RPksYRAtXGD3TKZuKYjpTBdFTCZEFAkF1E7hEa+eC2rFG5qbKMAo
+MMfHlhxxcJfgVYtTKnJCXPRqyiO9slk6DsMVrWqxF0asYhGhAnylicCMEl9K52vW
+XSZHt9lJ9lzRb1ma5E5bH0intolZCdDwku1s0+4UsPapuaD1jWVxcmnUW8j0Vwzw
+mwGpk0QA1DDCx1NpryDD7dbonZJcpA8f7Jkj98lZZZEUAO2qanG29cr0r4LLUdCa
+DsPQgjFU+nyutvPpMauD5Hf830UFYwIDAQABAoICAQCTVTcFCdl6MdSg4UwK0P/S
+fRCb/A0fJs67Agis6N9h/wI0NUyx7G6mLXU0si+U29KYGH0RKcgltJmKrYf8XoZR
+R3DvTTBfvs99QXd2G5hxTboMIPVcUi8nDE7PB+6XVkLP4hhP5uSpeqWNJZiQdTlh
+bKH2IgE8NQGyDpDMkPcKkvmw2GG/DiTtrwJ91fxRFRWzqN2LHMFMYWEHWtIR9Der
+xSC7q72om5s3fxvtIkUHwe5fwXvA9fbRAqezBR/9qL0LXTHowbpsuUz38SCuJD9g
+sfSlRxcsyly4pGf/FQpSiYKWcWlcSopKSzLDkyLqMc1GKlkGnu6aFJg95W63D1LS
+OaOXuYShHxLkqyhT8uQGRqDCu3E2ivb6fMxAPzJxxs3JrZvumNsqyxbp+HVF0idj
+NijMN/8Kb4KmNHG9I3SHG61tQFYDtxoMMNiHzq3fafBJnVcf6iThQdE5pGLN2OdF
+3rcSTeHI2HxhTrXtuiHmWXNk9aZ2TOhrssNZZjDkFL/KYh6G8guy+tn66YWy77VC
+id+6PBzYXTXsUauo4NWW1rLUfzT/y93IwVGpoXs7GHUUduZ6Q3PxsMTMF9IjBvqR
+JvfP84CUfGXoebVJmWyGhtW6N5ParvQxbonDitA0TPZcRyX3N8yqIGO/mb8MWA9M
+7s/xMZuksOpw5LSCoTAW6QKCAQEA9Z1WM/5XT+5HkwPBvS5aRz0SqMqPxAkZ0dNz
+O06zpXv6e2IPy40UzFWCIkyq3vWKQ5bqU1fnejUdmjvtnP+KhH6fxnQCgiunnrDq
+j1Sk2Y4gb1KyZY/C8IejOexM2qX7sfDTLI8XEvxJxVFCNmvYfnv4AX5QD8VOsjrg
+bvodLAgDSo2FVDP+mkpW7zAIoRV02l6QdZA+YcG940eqxB9sPR3/1KUHe9wUTTbJ
+FV5ahEPuXCyvRJkZ/rD5CPPZoQHfjKHxDlu7yfoatzCSYj10R+RInfqOFGVY4D/C
+2csXjymwTN4CFUnJcP410YhPFn5ekmc/E3xqPKgIDQhQ2+oivwKCAQEA2icN1iEo
+YuBJwB3pX3jrwk+1bpUXASueWhyAhSeNMrTrJ/lSgEAy9FBxJNnK1PjkABRnFhS+
+uxbC2hQdfAkNDS21PQOk6hOhebUVuBdfmKY1CL+P9y4Af0fjV1rgRGHihnZB5lKU
+1R/wFb8c4QgPwduiqDoZ5QP3dgxpZ4R3SCzuoyVelUSlgyWPoslg+yPddcnV8bTf
+BUlEOOyXgVudkSFlRpWZ+/ZAkLTj8rnrtKp8/+GtTQvJvPJHPfuSsDhI+EQVWqab
+HelMhxvIi9xOyN/OCc3Ex6JlEVa+X8EyNhQsV2sAKnld92hdEozW2uxRniIkxvL4
+CuBp/p3fWxEaXQKCAQEAw2j7RYCMnNZZ8Zhiko4HW3g2mT4XpYMMHMlbe4sBGJ8L
+yRBaurqzGmLJl1ph8+NsrpuqMMbWLn+F3sjhIjCZVxKbMbvopwHuaS4eYAya30/Z
+dFhaAL2g/dccQSBEgQzftFGC4YeydvNsCeW9hSjGZNNinGWPcwyqsNhw6Tpq7TYu
+0CjKNBTt8nlEsyYHJ4m3n2jvC+nIB+Spm+LP9Rt+9R0iBl+KFbwiFtCIqUyZPXQC
+dylCBJS+fsj0SXAg7J1d6ziIXcEUJfyrNqYZQLneAriYIcBPO+DqFfgEoVyYkNk9
+H9rd02wSLajCzsLhEWdW/KnSIEGzEDErvpqoIl8kZwKCAQEAjNO3T+sZyjKmCXqF
+xBcogsi4BAoEzsGcuOk7Yjn1Ia2/PI/r3VUUT7l6QOLD2JZPgWmqXovH0LjR0rw3
+iHHDViWSoS+wD1fa3tmyiqO0F7P7+ojHZDbzJTeAIE1PB3X1KP5AbnITGD5E25UD
+DJYKrgeeSmEvhDL6Vd+PT78ozZQL/Y/LLishebcOsXS0wYsWlMpV7XHoot34R5Mb
+/urond7kJRvASvJeHcxYdsHk0j1Y8kp6eIlKk0oICZBU0qOTH4m8C0gQTM/lkjay
+UO9IgM5RkOyfwowoGHhZ7zClvFlrgodVlRXCPkvGAYqfzLXPvnimKzSAQW07n53E
+qWIyFQKCAQAWRNG6hPCOzkNxMJ/RK7dwxZW6b4a1L3PMXTT/xrBKRIS1WNjbpkYO
+/FLIufOqJT6FQN2obM5uso3TI+R7MwH8DnTSnDDy0Hvs3CdHdtn2tapZOViF/UVv
+uCQa+/jMVKFCZ8k7pPFMIG6tB6WBA5MmJrW+8s0ouxLbRF5rZyzqOeyPdVBYYYDb
+68nGNA6GAtTQs9h7xV2tsQ1bXNP+6gqG3BgZYo+76xKITddT6s9aaC++LVBnPOdq
+LHV7gUvoBkVLjIp4L3Fb/DGMCcOVMCxmFlRBn+RBlV7slehvgq+Ywz2GHWLr+O/z
+V2NAtwvCfZE2Do/4f2mpHnamhS6AvrDe
+-----END PRIVATE KEY-----
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index e630da0..ffde68e 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -36,7 +36,7 @@
         "libvndksupport",
         "libziparchive",
         "libz",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V5-cpp",
     ],
 }
 
diff --git a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
new file mode 100644
index 0000000..5ab4dc6
--- /dev/null
+++ b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
@@ -0,0 +1,77 @@
+/*
+ * 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.fsverity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.rules.ExternalResource;
+
+public final class AddFsVerityCertRule extends ExternalResource {
+
+    private static final String APK_VERITY_STANDARD_MODE = "2";
+
+    private final BaseHostJUnit4Test mHost;
+    private final String mCertPath;
+    private String mKeyId;
+
+    public AddFsVerityCertRule(BaseHostJUnit4Test host, String certPath) {
+        mHost = host;
+        mCertPath = certPath;
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        ITestDevice device = mHost.getDevice();
+        String apkVerityMode = device.getProperty("ro.apk_verity.mode");
+        assumeTrue(device.getLaunchApiLevel() >= 30
+                || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+
+        String keyId = executeCommand(
+                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + mCertPath).trim();
+        assertThat(keyId).matches("^\\d+$");
+        mKeyId = keyId;
+    }
+
+    @Override
+    protected void after() {
+        if (mKeyId == null) return;
+        try {
+            executeCommand("mini-keyctl unlink " + mKeyId + " .fs-verity");
+        } catch (DeviceNotAvailableException e) {
+            LogUtil.CLog.e(e);
+        }
+        mKeyId = null;
+    }
+
+    private String executeCommand(String cmd) throws DeviceNotAvailableException {
+        CommandResult result = mHost.getDevice().executeShellV2Command(cmd);
+        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+                .that(result.getStatus())
+                .isEqualTo(CommandStatus.SUCCESS);
+        return result.getStdout();
+    }
+}
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
new file mode 100755
index 0000000..c07a98a
--- /dev/null
+++ b/tools/fonts/update_font_metadata.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import argparse
+
+from fontTools import ttLib
+
+
+def update_font_revision(font, revisionSpec):
+    if revisionSpec.startswith('+'):
+      font['head'].fontRevision += float(revisionSpec[1:])
+    else:
+      font['head'].fontRevision = float(revisionSpec)
+
+
+def main():
+    args_parser = argparse.ArgumentParser(description='Update font file metadata')
+    args_parser.add_argument('--input', help='Input otf/ttf font file.')
+    args_parser.add_argument('--output', help='Output file for updated font file.')
+    args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
+    args = args_parser.parse_args()
+
+    font = ttLib.TTFont(args.input)
+    update_font_revision(font, args.revision)
+    font.save(args.output)
+
+if __name__ == "__main__":
+    main()