Merge "Add perf test for Typeface serialization."
diff --git a/Android.bp b/Android.bp
index 9b8e018..8164d6a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,9 +22,9 @@
     ],
     errorprone: {
         javacflags: [
-            "-Xep:AndroidFrameworkBinderIdentity:ERROR",
+            // "-Xep:AndroidFrameworkBinderIdentity:ERROR",
             "-Xep:AndroidFrameworkCompatChange:ERROR",
-            "-Xep:AndroidFrameworkUid:ERROR",
+            // "-Xep:AndroidFrameworkUid:ERROR",
             // NOTE: only enable to generate local patchfiles
             // "-XepPatchChecks:refaster:frameworks/base/errorprone/refaster/EfficientXml.java.refaster",
             // "-XepPatchLocation:/tmp/refaster/",
@@ -44,6 +44,7 @@
             "-Xep:AndroidFrameworkEfficientCollections:OFF",
             "-Xep:AndroidFrameworkEfficientParcelable:OFF",
             "-Xep:AndroidFrameworkEfficientStrings:OFF",
+            "-Xep:AndroidFrameworkEfficientXml:OFF",
         ],
     },
 }
@@ -527,6 +528,7 @@
         "android.hardware.vibrator-V1.3-java",
         "android.system.keystore2-java",
         "android.system.suspend.control.internal-java",
+        "cameraprotosnano",
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
@@ -626,6 +628,7 @@
         "av-types-aidl-java",
         "mediatranscoding_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
+        "modules-utils-os",
     ],
 }
 
@@ -1267,7 +1270,6 @@
 filegroup {
     name: "framework-telephony-common-shared-srcs",
     srcs: [
-        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/os/RegistrantList.java",
         "core/java/android/os/Registrant.java",
         "core/java/android/util/IndentingPrintWriter.java",
@@ -1350,7 +1352,6 @@
     name: "framework-wifi-service-shared-srcs",
     srcs: [
         "core/java/android/net/InterfaceConfiguration.java",
-        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/util/BackupUtils.java",
         "core/java/android/util/Rational.java",
         "core/java/com/android/internal/util/FastXmlSerializer.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 49a42d7..380839e 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -70,6 +70,7 @@
         "android.hardware.cas-V1.2-java",
         "android.hardware.health-V1.0-java-constants",
         "android.hardware.radio-V1.5-java",
+        "android.hardware.radio-V1.6-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
@@ -252,21 +253,7 @@
         "framework-wifi.stubs",
         "private-stub-annotations-jar",
     ],
-    defaults: [
-        "android_defaults_stubs_current",
-        "android_stubs_dists_default",
-    ],
-    dist: {
-        dir: "apistubs/android/system",
-    },
-    dists: [
-        {
-            // Legacy dist path
-            targets: ["sdk", "win_sdk"],
-            tag: ".jar",
-            dest: "android_system.jar",
-        },
-    ],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
@@ -285,7 +272,21 @@
         "framework-wifi.stubs.system",
         "private-stub-annotations-jar",
     ],
-    defaults: ["android_defaults_stubs_current"],
+    defaults: [
+        "android_defaults_stubs_current",
+        "android_stubs_dists_default",
+    ],
+    dist: {
+        dir: "apistubs/android/system",
+    },
+    dists: [
+        {
+            // Legacy dist path
+            targets: ["sdk", "win_sdk"],
+            tag: ".jar",
+            dest: "android_system.jar",
+        },
+    ],
 }
 
 java_library_static {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
index beb9ad3..9ca363e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java
@@ -16,10 +16,8 @@
 
 package android.app.appsearch;
 
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-
 import android.app.appsearch.AppSearchSchema.PropertyConfig;
 
 /**
@@ -29,9 +27,8 @@
  *
  * @hide
  */
-
 public class AppSearchEmail extends GenericDocument {
-    /** The name of the schema type for {@link AppSearchEmail} documents.*/
+    /** The name of the schema type for {@link AppSearchEmail} documents. */
     public static final String SCHEMA_TYPE = "builtin:Email";
 
     private static final String KEY_FROM = "from";
@@ -41,54 +38,55 @@
     private static final String KEY_SUBJECT = "subject";
     private static final String KEY_BODY = "body";
 
-    public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
-            .addProperty(new PropertyConfig.Builder(KEY_FROM)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).addProperty(new PropertyConfig.Builder(KEY_TO)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).addProperty(new PropertyConfig.Builder(KEY_CC)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).addProperty(new PropertyConfig.Builder(KEY_BCC)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).addProperty(new PropertyConfig.Builder(KEY_SUBJECT)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).addProperty(new PropertyConfig.Builder(KEY_BODY)
-                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                    .build()
-
-            ).build();
+    public static final AppSearchSchema SCHEMA =
+            new AppSearchSchema.Builder(SCHEMA_TYPE)
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_FROM)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_TO)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_CC)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_BCC)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_SUBJECT)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .addProperty(
+                            new PropertyConfig.Builder(KEY_BODY)
+                                    .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                    .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                    .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                    .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                    .build())
+                    .build();
 
     /**
-     * Creates a new {@link AppSearchEmail} from the contents of an existing
-     * {@link GenericDocument}.
+     * Creates a new {@link AppSearchEmail} from the contents of an existing {@link
+     * GenericDocument}.
      *
      * @param document The {@link GenericDocument} containing the email content.
      */
@@ -109,8 +107,8 @@
     /**
      * Gets the destination addresses of {@link AppSearchEmail}.
      *
-     * @return The destination addresses of {@link AppSearchEmail} or {@code null} if it's not
-     *         been set yet.
+     * @return The destination addresses of {@link AppSearchEmail} or {@code null} if it's not been
+     *     set yet.
      */
     @Nullable
     public String[] getTo() {
@@ -157,9 +155,7 @@
         return getPropertyString(KEY_BODY);
     }
 
-    /**
-     * The builder class for {@link AppSearchEmail}.
-     */
+    /** The builder class for {@link AppSearchEmail}. */
     public static class Builder extends GenericDocument.Builder<AppSearchEmail.Builder> {
 
         /**
@@ -171,54 +167,42 @@
             super(uri, SCHEMA_TYPE);
         }
 
-        /**
-         * Sets the from address of {@link AppSearchEmail}
-         */
+        /** Sets the from address of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setFrom(@NonNull String from) {
             setPropertyString(KEY_FROM, from);
             return this;
         }
 
-        /**
-         * Sets the destination address of {@link AppSearchEmail}
-         */
+        /** Sets the destination address of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setTo(@NonNull String... to) {
             setPropertyString(KEY_TO, to);
             return this;
         }
 
-        /**
-         * Sets the CC list of {@link AppSearchEmail}
-         */
+        /** Sets the CC list of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setCc(@NonNull String... cc) {
             setPropertyString(KEY_CC, cc);
             return this;
         }
 
-        /**
-         * Sets the BCC list of {@link AppSearchEmail}
-         */
+        /** Sets the BCC list of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setBcc(@NonNull String... bcc) {
             setPropertyString(KEY_BCC, bcc);
             return this;
         }
 
-        /**
-         * Sets the subject of {@link AppSearchEmail}
-         */
+        /** Sets the subject of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setSubject(@NonNull String subject) {
             setPropertyString(KEY_SUBJECT, subject);
             return this;
         }
 
-        /**
-         * Sets the body of {@link AppSearchEmail}
-         */
+        /** Sets the body of {@link AppSearchEmail} */
         @NonNull
         public AppSearchEmail.Builder setBody(@NonNull String body) {
             setPropertyString(KEY_BODY, body);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
index 3933726..50583ad 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -16,15 +16,14 @@
 
 package android.app.appsearch;
 
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.IllegalSchemaException;
+import android.os.Bundle;
 import android.util.ArraySet;
+
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -40,6 +39,8 @@
  * <p>For example, an e-mail message or a music recording could be a schema type.
  *
  * <p>The schema consists of type information, properties, and config (like tokenization type).
+ *
+ * @see AppSearchSession#setSchema
  * @hide
  */
 public final class AppSearchSchema {
@@ -49,7 +50,6 @@
     private final Bundle mBundle;
 
     /** @hide */
-    
     public AppSearchSchema(@NonNull Bundle bundle) {
         Preconditions.checkNotNull(bundle);
         mBundle = bundle;
@@ -57,9 +57,9 @@
 
     /**
      * Returns the {@link Bundle} populated by this builder.
+     *
      * @hide
      */
-    
     @NonNull
     public Bundle getBundle() {
         return mBundle;
@@ -72,7 +72,7 @@
 
     /** Returns the name of this schema type, e.g. Email. */
     @NonNull
-    public String getSchemaTypeName() {
+    public String getSchemaType() {
         return mBundle.getString(SCHEMA_TYPE_FIELD, "");
     }
 
@@ -144,8 +144,8 @@
     /**
      * Configuration for a single property (field) of a document type.
      *
-     * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
-     * a property.
+     * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a
+     * property.
      */
     public static final class PropertyConfig {
         private static final String NAME_FIELD = "name";
@@ -157,18 +157,20 @@
 
         /**
          * Physical data-types of the contents of the property.
+         *
          * @hide
          */
         // NOTE: The integer values of these constants must match the proto enum constants in
         // com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
-        @IntDef(value = {
-                DATA_TYPE_STRING,
-                DATA_TYPE_INT64,
-                DATA_TYPE_DOUBLE,
-                DATA_TYPE_BOOLEAN,
-                DATA_TYPE_BYTES,
-                DATA_TYPE_DOCUMENT,
-        })
+        @IntDef(
+                value = {
+                    DATA_TYPE_STRING,
+                    DATA_TYPE_INT64,
+                    DATA_TYPE_DOUBLE,
+                    DATA_TYPE_BOOLEAN,
+                    DATA_TYPE_BYTES,
+                    DATA_TYPE_DOCUMENT,
+                })
         @Retention(RetentionPolicy.SOURCE)
         public @interface DataType {}
 
@@ -181,23 +183,25 @@
         public static final int DATA_TYPE_BYTES = 5;
 
         /**
-         * Indicates that the property itself is an Document, making it part a hierarchical
-         * Document schema. Any property using this DataType MUST have a valid
-         * {@code schemaType}.
+         * Indicates that the property is itself a {@link GenericDocument}, making it part of a
+         * hierarchical schema. Any property using this DataType MUST have a valid {@link
+         * PropertyConfig#getSchemaType}.
          */
         public static final int DATA_TYPE_DOCUMENT = 6;
 
         /**
          * The cardinality of the property (whether it is required, optional or repeated).
+         *
          * @hide
          */
         // NOTE: The integer values of these constants must match the proto enum constants in
         // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
-        @IntDef(value = {
-                CARDINALITY_REPEATED,
-                CARDINALITY_OPTIONAL,
-                CARDINALITY_REQUIRED,
-        })
+        @IntDef(
+                value = {
+                    CARDINALITY_REPEATED,
+                    CARDINALITY_OPTIONAL,
+                    CARDINALITY_REQUIRED,
+                })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Cardinality {}
 
@@ -212,23 +216,25 @@
 
         /**
          * Encapsulates the configurations on how AppSearch should query/index these terms.
+         *
          * @hide
          */
-        @IntDef(value = {
-                INDEXING_TYPE_NONE,
-                INDEXING_TYPE_EXACT_TERMS,
-                INDEXING_TYPE_PREFIXES,
-        })
+        @IntDef(
+                value = {
+                    INDEXING_TYPE_NONE,
+                    INDEXING_TYPE_EXACT_TERMS,
+                    INDEXING_TYPE_PREFIXES,
+                })
         @Retention(RetentionPolicy.SOURCE)
         public @interface IndexingType {}
 
         /**
          * Content in this property will not be tokenized or indexed.
          *
-         * <p>Useful if the data type is not made up of terms (e.g.
-         * {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
-         * type). All the properties inside the nested property won't be indexed regardless of the
-         * value of {@code indexingType} for the nested properties.
+         * <p>Useful if the data type is not made up of terms (e.g. {@link
+         * PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES} type). None
+         * of the properties inside the nested property will be indexed regardless of the value of
+         * {@code indexingType} for the nested properties.
          */
         public static final int INDEXING_TYPE_NONE = 0;
 
@@ -250,20 +256,22 @@
 
         /**
          * Configures how tokens should be extracted from this property.
+         *
          * @hide
          */
         // NOTE: The integer values of these constants must match the proto enum constants in
         // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
-        @IntDef(value = {
-                TOKENIZER_TYPE_NONE,
-                TOKENIZER_TYPE_PLAIN,
-        })
+        @IntDef(
+                value = {
+                    TOKENIZER_TYPE_NONE,
+                    TOKENIZER_TYPE_PLAIN,
+                })
         @Retention(RetentionPolicy.SOURCE)
         public @interface TokenizerType {}
 
         /**
-         * It is only valid for tokenizer_type to be 'NONE' if the data type is
-         * {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
+         * It is only valid for tokenizer_type to be 'NONE' if the data type is {@link
+         * PropertyConfig#DATA_TYPE_DOCUMENT}.
          */
         public static final int TOKENIZER_TYPE_NONE = 0;
 
@@ -295,8 +303,8 @@
         /**
          * Returns the logical schema-type of the contents of this property.
          *
-         * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}.
-         * Otherwise, it is {@code null}.
+         * <p>Only set when {@link #getDataType} is set to {@link #DATA_TYPE_DOCUMENT}. Otherwise,
+         * it is {@code null}.
          */
         @Nullable
         public String getSchemaType() {
@@ -325,9 +333,10 @@
          *
          * <p>The following properties must be set, or {@link PropertyConfig} construction will
          * fail:
+         *
          * <ul>
-         *     <li>dataType
-         *     <li>cardinality
+         *   <li>dataType
+         *   <li>cardinality
          * </ul>
          *
          * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
@@ -359,8 +368,8 @@
             /**
              * The logical schema-type of the contents of this property.
              *
-             * <p>Only required when {@link #setDataType} is set to
-             * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
+             * <p>Only required when {@link #setDataType} is set to {@link #DATA_TYPE_DOCUMENT}.
+             * Otherwise, it is ignored.
              */
             @NonNull
             public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
index 48d3ac0..f41fa8d 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
@@ -16,15 +16,14 @@
 
 package android.app.appsearch;
 
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.util.Log;
-
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.util.Log;
+
 import com.android.internal.util.Preconditions;
 
 import java.lang.reflect.Array;
@@ -37,17 +36,21 @@
  * Represents a document unit.
  *
  * <p>Documents are constructed via {@link GenericDocument.Builder}.
+ *
+ * @see AppSearchSession#putDocuments
+ * @see AppSearchSession#getByUri
+ * @see AppSearchSession#query
  * @hide
  */
 public class GenericDocument {
     private static final String TAG = "GenericDocument";
 
-    /** The default empty namespace.*/
+    /** The default empty namespace. */
     public static final String DEFAULT_NAMESPACE = "";
 
     /**
-     * The maximum number of elements in a repeatable field. Will reject the request if exceed
-     * this limit.
+     * The maximum number of elements in a repeatable field. Will reject the request if exceed this
+     * limit.
      */
     private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
 
@@ -78,47 +81,43 @@
     /**
      * The maximum number of indexed properties a document can have.
      *
-     * <p>Indexed properties are properties where the
-     * {@link android.app.appsearch.annotation.AppSearchDocument.Property#indexingType} constant is
-     * anything other than {@link
-     * android.app.appsearch.AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
+     * <p>Indexed properties are properties where the {@link
+     * AppSearchSchema.PropertyConfig#getIndexingType()} constant is anything other than {@link
+     * AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
      */
     public static int getMaxIndexedProperties() {
         return MAX_INDEXED_PROPERTIES;
     }
 
-    /** Contains {@link GenericDocument} basic information (uri, schemaType etc).*/
-    @NonNull
-    final Bundle mBundle;
+    /** Contains {@link GenericDocument} basic information (uri, schemaType etc). */
+    @NonNull final Bundle mBundle;
 
-    /** Contains all properties in {@link GenericDocument} to support getting properties via keys.*/
-    @NonNull
-    private final Bundle mProperties;
+    /**
+     * Contains all properties in {@link GenericDocument} to support getting properties via keys.
+     */
+    @NonNull private final Bundle mProperties;
 
-    @NonNull
-    private final String mUri;
-    @NonNull
-    private final String mSchemaType;
+    @NonNull private final String mUri;
+    @NonNull private final String mSchemaType;
     private final long mCreationTimestampMillis;
-    @Nullable
-    private Integer mHashCode;
+    @Nullable private Integer mHashCode;
 
     /**
      * Rebuilds a {@link GenericDocument} by the a bundle.
-     * @param bundle Contains {@link GenericDocument} basic information (uri, schemaType etc) and
-     *               a properties bundle contains all properties in {@link GenericDocument} to
-     *               support getting properties via keys.
+     *
+     * @param bundle Contains {@link GenericDocument} basic information (uri, schemaType etc) and a
+     *     properties bundle contains all properties in {@link GenericDocument} to support getting
+     *     properties via keys.
      * @hide
      */
-    
     public GenericDocument(@NonNull Bundle bundle) {
         Preconditions.checkNotNull(bundle);
         mBundle = bundle;
         mProperties = Preconditions.checkNotNull(bundle.getParcelable(PROPERTIES_FIELD));
         mUri = Preconditions.checkNotNull(mBundle.getString(URI_FIELD));
         mSchemaType = Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
-        mCreationTimestampMillis = mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD,
-                System.currentTimeMillis());
+        mCreationTimestampMillis =
+                mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
     }
 
     /**
@@ -132,9 +131,9 @@
 
     /**
      * Returns the {@link Bundle} populated by this builder.
+     *
      * @hide
      */
-    
     @NonNull
     public Bundle getBundle() {
         return mBundle;
@@ -158,7 +157,11 @@
         return mSchemaType;
     }
 
-    /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
+    /**
+     * Returns the creation timestamp of the {@link GenericDocument}, in milliseconds.
+     *
+     * <p>The value is in the {@link System#currentTimeMillis} time base.
+     */
     public long getCreationTimestampMillis() {
         return mCreationTimestampMillis;
     }
@@ -166,8 +169,12 @@
     /**
      * Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
      *
+     * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+     * {@code creationTimestampMillis + ttlMillis}, measured in the {@link System#currentTimeMillis}
+     * time base, the document will be auto-deleted.
+     *
      * <p>The default value is 0, which means the document is permanent and won't be auto-deleted
-     *    until the app is uninstalled.
+     * until the app is uninstalled.
      */
     public long getTtlMillis() {
         return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
@@ -179,7 +186,10 @@
      * <p>The score is a query-independent measure of the document's quality, relative to other
      * {@link GenericDocument}s of the same type.
      *
-     * <p>The default value is 0.
+     * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
+     * Documents with higher scores are considered better than documents with lower scores.
+     *
+     * <p>Any nonnegative integer can be used a score.
      */
     public int getScore() {
         return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
@@ -195,8 +205,8 @@
      * Retrieves a {@link String} value by key.
      *
      * @param key The key to look for.
-     * @return The first {@link String} associated with the given key or {@code null} if there
-     *         is no such key or the value is of a different type.
+     * @return The first {@link String} associated with the given key or {@code null} if there is no
+     *     such key or the value is of a different type.
      */
     @Nullable
     public String getPropertyString(@NonNull String key) {
@@ -214,7 +224,7 @@
      *
      * @param key The key to look for.
      * @return The first {@code long} associated with the given key or default value {@code 0} if
-     *         there is no such key or the value is of a different type.
+     *     there is no such key or the value is of a different type.
      */
     public long getPropertyLong(@NonNull String key) {
         Preconditions.checkNotNull(key);
@@ -231,7 +241,7 @@
      *
      * @param key The key to look for.
      * @return The first {@code double} associated with the given key or default value {@code 0.0}
-     *         if there is no such key or the value is of a different type.
+     *     if there is no such key or the value is of a different type.
      */
     public double getPropertyDouble(@NonNull String key) {
         Preconditions.checkNotNull(key);
@@ -247,8 +257,8 @@
      * Retrieves a {@code boolean} value by key.
      *
      * @param key The key to look for.
-     * @return The first {@code boolean} associated with the given key or default value
-     *         {@code false} if there is no such key or the value is of a different type.
+     * @return The first {@code boolean} associated with the given key or default value {@code
+     *     false} if there is no such key or the value is of a different type.
      */
     public boolean getPropertyBoolean(@NonNull String key) {
         Preconditions.checkNotNull(key);
@@ -264,8 +274,8 @@
      * Retrieves a {@code byte[]} value by key.
      *
      * @param key The key to look for.
-     * @return The first {@code byte[]} associated with the given key or {@code null} if there
-     *         is no such key or the value is of a different type.
+     * @return The first {@code byte[]} associated with the given key or {@code null} if there is no
+     *     such key or the value is of a different type.
      */
     @Nullable
     public byte[] getPropertyBytes(@NonNull String key) {
@@ -283,7 +293,7 @@
      *
      * @param key The key to look for.
      * @return The first {@link GenericDocument} associated with the given key or {@code null} if
-     *         there is no such key or the value is of a different type.
+     *     there is no such key or the value is of a different type.
      */
     @Nullable
     public GenericDocument getPropertyDocument(@NonNull String key) {
@@ -300,10 +310,18 @@
     private static void warnIfSinglePropertyTooLong(
             @NonNull String propertyType, @NonNull String key, int propertyLength) {
         if (propertyLength > 1) {
-            Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength
-                    + " elements. Only the first one will be returned from "
-                    + "getProperty" + propertyType + "(). Try getProperty" + propertyType
-                    + "Array().");
+            Log.w(
+                    TAG,
+                    "The value for \""
+                            + key
+                            + "\" contains "
+                            + propertyLength
+                            + " elements. Only the first one will be returned from "
+                            + "getProperty"
+                            + propertyType
+                            + "(). Try getProperty"
+                            + propertyType
+                            + "Array().");
         }
     }
 
@@ -311,8 +329,8 @@
      * Retrieves a repeated {@code String} property by key.
      *
      * @param key The key to look for.
-     * @return The {@code String[]} associated with the given key, or {@code null} if no value
-     *         is set or the value is of a different type.
+     * @return The {@code String[]} associated with the given key, or {@code null} if no value is
+     *     set or the value is of a different type.
      */
     @Nullable
     public String[] getPropertyStringArray(@NonNull String key) {
@@ -324,8 +342,8 @@
      * Retrieves a repeated {@link String} property by key.
      *
      * @param key The key to look for.
-     * @return The {@code long[]} associated with the given key, or {@code null} if no value is
-     *         set or the value is of a different type.
+     * @return The {@code long[]} associated with the given key, or {@code null} if no value is set
+     *     or the value is of a different type.
      */
     @Nullable
     public long[] getPropertyLongArray(@NonNull String key) {
@@ -337,8 +355,8 @@
      * Retrieves a repeated {@code double} property by key.
      *
      * @param key The key to look for.
-     * @return The {@code double[]} associated with the given key, or {@code null} if no value
-     *         is set or the value is of a different type.
+     * @return The {@code double[]} associated with the given key, or {@code null} if no value is
+     *     set or the value is of a different type.
      */
     @Nullable
     public double[] getPropertyDoubleArray(@NonNull String key) {
@@ -350,8 +368,8 @@
      * Retrieves a repeated {@code boolean} property by key.
      *
      * @param key The key to look for.
-     * @return The {@code boolean[]} associated with the given key, or {@code null} if no value
-     *         is set or the value is of a different type.
+     * @return The {@code boolean[]} associated with the given key, or {@code null} if no value is
+     *     set or the value is of a different type.
      */
     @Nullable
     public boolean[] getPropertyBooleanArray(@NonNull String key) {
@@ -363,8 +381,8 @@
      * Retrieves a {@code byte[][]} property by key.
      *
      * @param key The key to look for.
-     * @return The {@code byte[][]} associated with the given key, or {@code null} if no value
-     *         is set or the value is of a different type.
+     * @return The {@code byte[][]} associated with the given key, or {@code null} if no value is
+     *     set or the value is of a different type.
      */
     @SuppressLint("ArrayReturn")
     @Nullable
@@ -397,7 +415,7 @@
      *
      * @param key The key to look for.
      * @return The {@link GenericDocument}[] associated with the given key, or {@code null} if no
-     *         value is set or the value is of a different type.
+     *     value is set or the value is of a different type.
      */
     @SuppressLint("ArrayReturn")
     @Nullable
@@ -419,8 +437,8 @@
     }
 
     /**
-     * Gets a repeated property of the given key, and casts it to the given class type, which
-     * must be an array class type.
+     * Gets a repeated property of the given key, and casts it to the given class type, which must
+     * be an array class type.
      */
     @Nullable
     private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) {
@@ -449,8 +467,9 @@
     }
 
     /**
-     * Deeply checks two bundles are equally or not.
-     * <p> Two bundles will be considered equally if they contain same content.
+     * Deeply checks whether two bundles are equal.
+     *
+     * <p>Two bundles will be considered equal if they contain the same content.
      */
     @SuppressWarnings("unchecked")
     private static boolean bundleEquals(Bundle one, Bundle two) {
@@ -537,8 +556,9 @@
 
     /**
      * Calculates the hash code for a bundle.
-     * <p> The hash code is only effected by the contents in the bundle. Bundles will get
-     * consistent hash code if they have same contents.
+     *
+     * <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent
+     * hash code if they have same contents.
      */
     @SuppressWarnings("unchecked")
     private static int bundleHashCode(Bundle bundle) {
@@ -663,11 +683,11 @@
          * Create a new {@link GenericDocument.Builder}.
          *
          * @param uri The uri of {@link GenericDocument}.
-         * @param schemaType The schema type of the {@link GenericDocument}. The passed-in
-         *        {@code schemaType} must be defined using {@link AppSearchSession#setSchema} prior
-         *        to inserting a document of this {@code schemaType} into the AppSearch index using
-         *        {@link AppSearchSession#putDocuments}. Otherwise, the document will be
-         *        rejected by {@link AppSearchSession#putDocuments}.
+         * @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}.
          */
         @SuppressWarnings("unchecked")
         public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -678,16 +698,16 @@
             mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType);
             mBundle.putString(GenericDocument.NAMESPACE_FIELD, DEFAULT_NAMESPACE);
             // Set current timestamp for creation timestamp by default.
-            mBundle.putLong(GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD,
-                    System.currentTimeMillis());
+            mBundle.putLong(
+                    GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
             mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
             mBundle.putInt(GenericDocument.SCORE_FIELD, DEFAULT_SCORE);
             mBundle.putBundle(PROPERTIES_FIELD, mProperties);
         }
 
         /**
-         * Sets the app-defined namespace this Document resides in. No special values are
-         * reserved or understood by the infrastructure.
+         * Sets the app-defined namespace this Document resides in. No special values are reserved
+         * or understood by the infrastructure.
          *
          * <p>URIs are unique within a namespace.
          *
@@ -702,8 +722,13 @@
         /**
          * Sets the score of the {@link GenericDocument}.
          *
-         * <p>The score is a query-independent measure of the document's quality, relative to
-         * other {@link GenericDocument}s of the same type.
+         * <p>The score is a query-independent measure of the document's quality, relative to other
+         * {@link GenericDocument}s of the same type.
+         *
+         * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
+         * Documents with higher scores are considered better than documents with lower scores.
+         *
+         * <p>Any nonnegative integer can be used a score.
          *
          * @throws IllegalArgumentException If the provided value is negative.
          */
@@ -718,22 +743,28 @@
         }
 
         /**
-         * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds. Should be
-         * set using a value obtained from the {@link System#currentTimeMillis()} time base.
+         * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
+         *
+         * <p>Should be set using a value obtained from the {@link System#currentTimeMillis} time
+         * base.
          */
         @NonNull
         public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putLong(GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD,
-                    creationTimestampMillis);
+            mBundle.putLong(
+                    GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, creationTimestampMillis);
             return mBuilderTypeInstance;
         }
 
         /**
          * Sets the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
          *
-         * <p>After this many milliseconds since the {@link #setCreationTimestampMillis creation
-         * timestamp}, the document is deleted.
+         * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+         * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
+         * System#currentTimeMillis} time base, the document will be auto-deleted.
+         *
+         * <p>The default value is 0, which means the document is permanent and won't be
+         * auto-deleted until the app is uninstalled.
          *
          * @param ttlMillis A non-negative duration in milliseconds.
          * @throws IllegalArgumentException If the provided value is negative.
@@ -749,8 +780,7 @@
         }
 
         /**
-         * Sets one or multiple {@code String} values for a property, replacing its previous
-         * values.
+         * Sets one or multiple {@code String} values for a property, replacing its previous values.
          *
          * @param key The key associated with the {@code values}.
          * @param values The {@code String} values of the property.
@@ -781,8 +811,7 @@
         }
 
         /**
-         * Sets one or multiple {@code long} values for a property, replacing its previous
-         * values.
+         * Sets one or multiple {@code long} values for a property, replacing its previous values.
          *
          * @param key The key associated with the {@code values}.
          * @param values The {@code long} values of the property.
@@ -797,8 +826,7 @@
         }
 
         /**
-         * Sets one or multiple {@code double} values for a property, replacing its previous
-         * values.
+         * Sets one or multiple {@code double} values for a property, replacing its previous values.
          *
          * @param key The key associated with the {@code values}.
          * @param values The {@code double} values of the property.
@@ -851,9 +879,14 @@
                 if (values[i] == null) {
                     throw new IllegalArgumentException("The String at " + i + " is null.");
                 } else if (values[i].length() > MAX_STRING_LENGTH) {
-                    throw new IllegalArgumentException("The String at " + i + " length is: "
-                            + values[i].length()  + ", which exceeds length limit: "
-                            + MAX_STRING_LENGTH + ".");
+                    throw new IllegalArgumentException(
+                            "The String at "
+                                    + i
+                                    + " length is: "
+                                    + values[i].length()
+                                    + ", which exceeds length limit: "
+                                    + MAX_STRING_LENGTH
+                                    + ".");
                 }
             }
             mProperties.putStringArray(key, values);
@@ -911,7 +944,10 @@
                 throw new IllegalArgumentException("The input array is empty.");
             } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
                 throw new IllegalArgumentException(
-                        "Repeated property \"" + key + "\" has length " + length
+                        "Repeated property \""
+                                + key
+                                + "\" has length "
+                                + length
                                 + ", which exceeds the limit of "
                                 + MAX_REPEATED_PROPERTY_LENGTH);
             }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
index e1e0eda..053d401 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GetByUriRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.util.ArraySet;
+
 import com.android.internal.util.Preconditions;
 
 import java.util.Arrays;
@@ -28,7 +29,7 @@
 /**
  * Encapsulates a request to retrieve documents by namespace and URI.
  *
- * @see AppSearchManager#getByUri
+ * @see AppSearchSession#getByUri
  * @hide
  */
 public final class GetByUriRequest {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
index 1f90bc1..42f1ff2 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/PutDocumentsRequest.java
@@ -16,10 +16,10 @@
 
 package android.app.appsearch;
 
-import android.annotation.SuppressLint;
-
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.AppSearchException;
+
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
@@ -29,9 +29,9 @@
 import java.util.List;
 
 /**
- * Encapsulates a request to index a document into an {@link AppSearchManager} database.
+ * Encapsulates a request to index a document into an {@link AppSearchSession} database.
  *
- * @see AppSearchManager#putDocuments
+ * @see AppSearchSession#putDocuments
  * @hide
  */
 public final class PutDocumentsRequest {
@@ -53,7 +53,7 @@
         private boolean mBuilt = false;
 
         /** Adds one or more documents to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder")  // Merged list available from getDocuments()
+        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
         public Builder addGenericDocument(@NonNull GenericDocument... documents) {
             Preconditions.checkNotNull(documents);
@@ -61,7 +61,7 @@
         }
 
         /** Adds one or more documents to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder")  // Merged list available from getDocuments()
+        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
         public Builder addGenericDocument(@NonNull Collection<GenericDocument> documents) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
index 486857f..3d83c39 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/RemoveByUriRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.util.ArraySet;
+
 import com.android.internal.util.Preconditions;
 
 import java.util.Arrays;
@@ -28,7 +29,7 @@
 /**
  * Encapsulates a request to remove documents by namespace and URI.
  *
- * @see AppSearchManager#removeByUri
+ * @see AppSearchSession#removeByUri
  * @hide
  */
 public final class RemoveByUriRequest {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
index 99cb2f1..5f3c06a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
@@ -16,60 +16,61 @@
 
 package android.app.appsearch;
 
-import android.os.Bundle;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 
-import java.util.Objects;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
- * This class represents one of the results obtained from the query.
+ * This class represents one of the results obtained from an AppSearch query.
  *
- * <p>It contains the document which matched, information about which section(s) in the document
- * matched, and snippet information containing textual summaries of the document's match(es).
+ * <p>This allows clients to obtain:
+ *
+ * <ul>
+ *   <li>The document which matched, using {@link #getDocument}
+ *   <li>Information about which properties in the document matched, and "snippet" information
+ *       containing textual summaries of the document's matches, using {@link #getMatches}
+ * </ul>
+ *
+ * <p>"Snippet" refers to a substring of text from the content of document that is returned as a
+ * part of search result.
+ *
+ * @see SearchResults
  * @hide
  */
 public final class SearchResult {
     /** @hide */
-    
     public static final String DOCUMENT_FIELD = "document";
 
     /** @hide */
-    
     public static final String MATCHES_FIELD = "matches";
 
-    @NonNull
-    private final Bundle mBundle;
+    @NonNull private final Bundle mBundle;
 
-    @NonNull
-    private final Bundle mDocumentBundle;
+    @NonNull private final Bundle mDocumentBundle;
 
     /** Cache of the inflated document. Comes from inflating mDocumentBundle at first use. */
-    @Nullable
-    private GenericDocument mDocument;
+    @Nullable private GenericDocument mDocument;
 
     /**
      * Contains a list of MatchInfo bundles that matched the request.
      *
-     * Only populated when requested in both {@link SearchSpec.Builder#setSnippetCount} and
+     * <p>Only populated when requested in both {@link SearchSpec.Builder#setSnippetCount} and
      * {@link SearchSpec.Builder#setSnippetCountPerProperty}.
      *
      * @see #getMatches()
      */
-    @NonNull
-    private final List<Bundle> mMatchBundles;
+    @NonNull private final List<Bundle> mMatchBundles;
 
     /** Cache of the inflated matches. Comes from inflating mMatchBundles at first use. */
-    @Nullable
-    private List<MatchInfo> mMatches;
+    @Nullable private List<MatchInfo> mMatches;
 
     /** @hide */
-    
     public SearchResult(@NonNull Bundle bundle) {
         mBundle = Preconditions.checkNotNull(bundle);
         mDocumentBundle = Preconditions.checkNotNull(bundle.getBundle(DOCUMENT_FIELD));
@@ -77,7 +78,6 @@
     }
 
     /** @hide */
-    
     @NonNull
     public Bundle getBundle() {
         return mBundle;
@@ -85,6 +85,7 @@
 
     /**
      * Contains the matching {@link GenericDocument}.
+     *
      * @return Document object which matched the query.
      */
     @NonNull
@@ -98,10 +99,10 @@
     /**
      * Contains a list of Snippets that matched the request.
      *
-     * @return List of matches based on {@link SearchSpec}. If snippeting is disabled using
-     * {@link SearchSpec.Builder#setSnippetCount} or
-     * {@link SearchSpec.Builder#setSnippetCountPerProperty}, for all results after that
-     * value, this method returns an empty list.
+     * @return List of matches based on {@link SearchSpec}. If snippeting is disabled using {@link
+     *     SearchSpec.Builder#setSnippetCount} or {@link
+     *     SearchSpec.Builder#setSnippetCountPerProperty}, for all results after that value, this
+     *     method returns an empty list.
      */
     @NonNull
     public List<MatchInfo> getMatches() {
@@ -116,79 +117,94 @@
     }
 
     /**
-     * Snippet: It refers to a substring of text from the content of document that is returned as a
-     * part of search result.
-     * This class represents a match objects for any Snippets that might be present in
-     * {@link SearchResults} from query. Using this class
-     * user can get the full text, exact matches and Snippets of document content for a given match.
+     * This class represents a match objects for any Snippets that might be present in {@link
+     * SearchResults} from query. Using this class user can get the full text, exact matches and
+     * Snippets of document content for a given match.
      *
-     * <p>Class Example 1:
-     * A document contains following text in property subject:
+     * <p>Class Example 1: A document contains following text in property subject:
+     *
      * <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar.
      *
      * <p>If the queryExpression is "foo".
      *
      * <p>{@link MatchInfo#getPropertyPath()} returns "subject"
+     *
      * <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another
      * nonsense word that’s used a lot is bar."
+     *
      * <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32]
+     *
      * <p>{@link MatchInfo#getExactMatch()} returns "foo"
+     *
      * <p>{@link MatchInfo#getSnippetPosition()} returns [26, 33]
+     *
      * <p>{@link MatchInfo#getSnippet()} returns "is foo."
+     *
      * <p>
-     * <p>Class Example 2:
-     * A document contains a property name sender which contains 2 property names name and email, so
-     * we will have 2 property paths: {@code sender.name} and {@code sender.email}.
-     * <p>Let {@code sender.name = "Test Name Jr."} and
-     * {@code sender.email = "TestNameJr@gmail.com"}
+     *
+     * <p>Class Example 2: A document contains a property name sender which contains 2 property
+     * names name and email, so we will have 2 property paths: {@code sender.name} and {@code
+     * sender.email}.
+     *
+     * <p>Let {@code sender.name = "Test Name Jr."} and {@code sender.email =
+     * "TestNameJr@gmail.com"}
      *
      * <p>If the queryExpression is "Test". We will have 2 matches.
      *
-     * <p> Match-1
+     * <p>Match-1
+     *
      * <p>{@link MatchInfo#getPropertyPath()} returns "sender.name"
+     *
      * <p>{@link MatchInfo#getFullText()} returns "Test Name Jr."
+     *
      * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4]
+     *
      * <p>{@link MatchInfo#getExactMatch()} returns "Test"
+     *
      * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9]
+     *
      * <p>{@link MatchInfo#getSnippet()} returns "Test Name"
-     * <p> Match-2
+     *
+     * <p>Match-2
+     *
      * <p>{@link MatchInfo#getPropertyPath()} returns "sender.email"
+     *
      * <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
+     *
      * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20]
+     *
      * <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
+     *
      * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20]
+     *
      * <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
      */
     public static final class MatchInfo {
         /**
          * The path of the matching snippet property.
+         *
          * @hide
          */
-        
         public static final String PROPERTY_PATH_FIELD = "propertyPath";
 
         /**
          * The index of matching value in its property. A property may have multiple values. This
          * index indicates which value is the match.
+         *
          * @hide
          */
-        
         public static final String VALUES_INDEX_FIELD = "valuesIndex";
 
         /** @hide */
-        
         public static final String EXACT_MATCH_POSITION_LOWER_FIELD = "exactMatchPositionLower";
 
         /** @hide */
-        
         public static final String EXACT_MATCH_POSITION_UPPER_FIELD = "exactMatchPositionUpper";
 
         /** @hide */
-        
         public static final String WINDOW_POSITION_LOWER_FIELD = "windowPositionLower";
 
         /** @hide */
-        
         public static final String WINDOW_POSITION_UPPER_FIELD = "windowPositionUpper";
 
         private final String mFullText;
@@ -201,16 +217,18 @@
             mBundle = Preconditions.checkNotNull(bundle);
             Preconditions.checkNotNull(document);
             mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
-            mFullText = getPropertyValues(
-                    document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD));
+            mFullText =
+                    getPropertyValues(document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD));
         }
 
         /**
          * Gets the property path corresponding to the given entry.
+         *
          * <p>Property Path: '.' - delimited sequence of property names indicating which property in
          * the Document these snippets correspond to.
-         * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
-         * For class example 1 this returns "subject"
+         *
+         * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. For class
+         * example 1 this returns "subject"
          */
         @NonNull
         public String getPropertyPath() {
@@ -219,6 +237,7 @@
 
         /**
          * Gets the full text corresponding to the given entry.
+         *
          * <p>For class example this returns "A commonly used fake word is foo. Another nonsense
          * word that's used a lot is bar."
          */
@@ -229,20 +248,23 @@
 
         /**
          * Gets the exact {@link MatchRange} corresponding to the given entry.
+         *
          * <p>For class example 1 this returns [29, 32]
          */
         @NonNull
         public MatchRange getExactMatchPosition() {
             if (mExactMatchRange == null) {
-                mExactMatchRange = new MatchRange(
-                        mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
-                        mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
+                mExactMatchRange =
+                        new MatchRange(
+                                mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
+                                mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
             }
             return mExactMatchRange;
         }
 
         /**
-         * Gets the  {@link MatchRange} corresponding to the given entry.
+         * Gets the {@link MatchRange} corresponding to the given entry.
+         *
          * <p>For class example 1 this returns "foo"
          */
         @NonNull
@@ -252,26 +274,31 @@
 
         /**
          * Gets the snippet {@link MatchRange} corresponding to the given entry.
-         * <p>Only populated when set maxSnippetSize > 0 in
-         * {@link SearchSpec.Builder#setMaxSnippetSize}.
+         *
+         * <p>Only populated when set maxSnippetSize > 0 in {@link
+         * SearchSpec.Builder#setMaxSnippetSize}.
+         *
          * <p>For class example 1 this returns [29, 41].
          */
         @NonNull
         public MatchRange getSnippetPosition() {
             if (mWindowRange == null) {
-                mWindowRange = new MatchRange(
-                        mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
-                        mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
+                mWindowRange =
+                        new MatchRange(
+                                mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
+                                mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
             }
             return mWindowRange;
         }
 
         /**
          * Gets the snippet corresponding to the given entry.
+         *
          * <p>Snippet - Provides a subset of the content to display. Only populated when requested
-         * maxSnippetSize > 0. The size of this content can be changed by
-         * {@link SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of
-         * the matched token with content on either side clipped to token boundaries.
+         * maxSnippetSize > 0. The size of this content can be changed by {@link
+         * SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of the
+         * matched token with content on either side clipped to token boundaries.
+         *
          * <p>For class example 1 this returns "foo. Another"
          */
         @NonNull
@@ -302,11 +329,10 @@
     /**
      * Class providing the position range of matching information.
      *
-     * <p> All ranges are finite, and the left side of the range is always {@code <=} the right
-     * side of the range.
+     * <p>All ranges are finite, and the left side of the range is always {@code <=} the right side
+     * of the range.
      *
-     * <p> Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
-     *
+     * <p>Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
      */
     public static final class MatchRange {
         private final int mEnd;
@@ -314,18 +340,18 @@
 
         /**
          * Creates a new immutable range.
-         * <p> The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
+         *
+         * <p>The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
          * must be lesser or equal to {@code end}.
          *
          * @param start The start point (inclusive)
          * @param end The end point (exclusive)
          * @hide
          */
-        
         public MatchRange(int start, int end) {
             if (start > end) {
-                throw new IllegalArgumentException("Start point must be less than or equal to "
-                        + "end point");
+                throw new IllegalArgumentException(
+                        "Start point must be less than or equal to " + "end point");
             }
             mStart = start;
             mEnd = end;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
index 756d1b5..dbd09d6 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResultPage.java
@@ -16,10 +16,9 @@
 
 package android.app.appsearch;
 
-import android.os.Bundle;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 
 import com.android.internal.util.Preconditions;
 
@@ -29,19 +28,17 @@
 
 /**
  * This class represents a page of {@link SearchResult}s
+ *
  * @hide
  */
-
 public class SearchResultPage {
     public static final String RESULTS_FIELD = "results";
     public static final String NEXT_PAGE_TOKEN_FIELD = "nextPageToken";
     private final long mNextPageToken;
 
-    @Nullable
-    private List<SearchResult> mResults;
+    @Nullable private List<SearchResult> mResults;
 
-    @NonNull
-    private final Bundle mBundle;
+    @NonNull private final Bundle mBundle;
 
     public SearchResultPage(@NonNull Bundle bundle) {
         mBundle = Preconditions.checkNotNull(bundle);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
index 15acf10..885802d 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -16,14 +16,14 @@
 
 package android.app.appsearch;
 
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
-
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.exceptions.IllegalSearchSpecException;
+import android.os.Bundle;
+
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -52,10 +52,10 @@
     static final String MAX_SNIPPET_FIELD = "maxSnippet";
 
     /** @hide */
-    
     public static final int DEFAULT_NUM_PER_PAGE = 10;
 
-    // TODO(b/170371356): In framework, we may want these limits might be flag controlled.
+    // TODO(b/170371356): In framework, we may want these limits to be flag controlled.
+    //  If that happens, the @IntRange() directives in this class may have to change.
     private static final int MAX_NUM_PER_PAGE = 10_000;
     private static final int MAX_SNIPPET_COUNT = 10_000;
     private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
@@ -63,43 +63,45 @@
 
     /**
      * Term Match Type for the query.
+     *
      * @hide
      */
     // NOTE: The integer values of these constants must match the proto enum constants in
     // {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
-    @IntDef(value = {
-            TERM_MATCH_EXACT_ONLY,
-            TERM_MATCH_PREFIX
-    })
+    @IntDef(value = {TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX})
     @Retention(RetentionPolicy.SOURCE)
     public @interface TermMatch {}
 
     /**
      * Query terms will only match exact tokens in the index.
+     *
      * <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football".
      */
     public static final int TERM_MATCH_EXACT_ONLY = 1;
     /**
      * Query terms will match indexed tokens when the query term is a prefix of the token.
+     *
      * <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football".
      */
     public static final int TERM_MATCH_PREFIX = 2;
 
     /**
      * Ranking Strategy for query result.
+     *
      * @hide
      */
     // NOTE: The integer values of these constants must match the proto enum constants in
     // {@link ScoringSpecProto.RankingStrategy.Code}
-    @IntDef(value = {
-            RANKING_STRATEGY_NONE,
-            RANKING_STRATEGY_DOCUMENT_SCORE,
-            RANKING_STRATEGY_CREATION_TIMESTAMP
-    })
+    @IntDef(
+            value = {
+                RANKING_STRATEGY_NONE,
+                RANKING_STRATEGY_DOCUMENT_SCORE,
+                RANKING_STRATEGY_CREATION_TIMESTAMP
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface RankingStrategy {}
 
-    /** No Ranking, results are returned in arbitrary order.*/
+    /** No Ranking, results are returned in arbitrary order. */
     public static final int RANKING_STRATEGY_NONE = 0;
     /** Ranked by app-provided document scores. */
     public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1;
@@ -108,14 +110,12 @@
 
     /**
      * Order for query result.
+     *
      * @hide
      */
     // NOTE: The integer values of these constants must match the proto enum constants in
     // {@link ScoringSpecProto.Order.Code}
-    @IntDef(value = {
-            ORDER_DESCENDING,
-            ORDER_ASCENDING
-    })
+    @IntDef(value = {ORDER_DESCENDING, ORDER_ASCENDING})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Order {}
 
@@ -127,7 +127,6 @@
     private final Bundle mBundle;
 
     /** @hide */
-    
     public SearchSpec(@NonNull Bundle bundle) {
         Preconditions.checkNotNull(bundle);
         mBundle = bundle;
@@ -135,9 +134,9 @@
 
     /**
      * Returns the {@link Bundle} populated by this builder.
+     *
      * @hide
      */
-    
     @NonNull
     public Bundle getBundle() {
         return mBundle;
@@ -154,12 +153,12 @@
      * <p>If empty, the query will search over all schema types.
      */
     @NonNull
-    public List<String> getSchemas() {
-        List<String> schemas = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
-        if (schemas == null) {
+    public List<String> getSchemaTypes() {
+        List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
+        if (schemaTypes == null) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableList(schemas);
+        return Collections.unmodifiableList(schemaTypes);
     }
 
     /**
@@ -176,8 +175,8 @@
         return Collections.unmodifiableList(namespaces);
     }
 
-    /** Returns the number of results per page in the returned object. */
-    public int getNumPerPage() {
+    /** 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);
     }
 
@@ -222,14 +221,12 @@
             mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
         }
 
-        /**
-         * Indicates how the query terms should match {@code TermMatchCode} in the index.
-         */
+        /** Indicates how the query terms should match {@code TermMatchCode} in the index. */
         @NonNull
         public Builder setTermMatch(@TermMatch int termMatchTypeCode) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentInRange(termMatchTypeCode, TERM_MATCH_EXACT_ONLY,
-                    TERM_MATCH_PREFIX, "Term match type");
+            Preconditions.checkArgumentInRange(
+                    termMatchTypeCode, TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX, "Term match type");
             mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
             return this;
         }
@@ -241,10 +238,10 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchema(@NonNull String... schemaTypes) {
+        public Builder addSchemaType(@NonNull String... schemaTypes) {
             Preconditions.checkNotNull(schemaTypes);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            return addSchema(Arrays.asList(schemaTypes));
+            return addSchemaType(Arrays.asList(schemaTypes));
         }
 
         /**
@@ -254,7 +251,7 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchema(@NonNull Collection<String> schemaTypes) {
+        public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
             Preconditions.checkNotNull(schemaTypes);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mSchemaTypes.addAll(schemaTypes);
@@ -262,8 +259,9 @@
         }
 
         /**
-         * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
-         * have the specified namespaces.
+         * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
+         * the specified namespaces.
+         *
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
@@ -274,8 +272,9 @@
         }
 
         /**
-         * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
-         * have the specified namespaces.
+         * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that have
+         * the specified namespaces.
+         *
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
@@ -288,51 +287,56 @@
 
         /**
          * Sets the number of results per page in the returned object.
-         * <p> The default number of results per page is 10. And should be set in range [0, 10k].
+         *
+         * <p>The default number of results per page is 10.
          */
         @NonNull
-        public SearchSpec.Builder setNumPerPage(int numPerPage) {
+        public SearchSpec.Builder setResultCountPerPage(
+                @IntRange(from = 0, to = MAX_NUM_PER_PAGE) int numPerPage) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(numPerPage, 0, MAX_NUM_PER_PAGE, "NumPerPage");
             mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
             return this;
         }
 
-        /** Sets ranking strategy for AppSearch results.*/
+        /** Sets ranking strategy for AppSearch results. */
         @NonNull
         public Builder setRankingStrategy(@RankingStrategy int rankingStrategy) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
-                    RANKING_STRATEGY_CREATION_TIMESTAMP, "Result ranking strategy");
+            Preconditions.checkArgumentInRange(
+                    rankingStrategy,
+                    RANKING_STRATEGY_NONE,
+                    RANKING_STRATEGY_CREATION_TIMESTAMP,
+                    "Result ranking strategy");
             mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
             return this;
         }
 
         /**
-         * Indicates the order of returned search results, the default is DESC, meaning that results
-         * with higher scores come first.
+         * Indicates the order of returned search results, the default is {@link #ORDER_DESCENDING},
+         * meaning that results with higher scores come first.
+         *
          * <p>This order field will be ignored if RankingStrategy = {@code RANKING_STRATEGY_NONE}.
          */
         @NonNull
         public Builder setOrder(@Order int order) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
-                    "Result ranking order");
+            Preconditions.checkArgumentInRange(
+                    order, ORDER_DESCENDING, ORDER_ASCENDING, "Result ranking order");
             mBundle.putInt(ORDER_FIELD, order);
             return this;
         }
 
         /**
-         * Only the first {@code snippetCount} documents based on the ranking strategy
-         * will have snippet information provided.
+         * Only the first {@code snippetCount} documents based on the ranking strategy will have
+         * snippet information provided.
          *
          * <p>If set to 0 (default), snippeting is disabled and {@link SearchResult#getMatches} will
          * return {@code null} for that result.
-         *
-         * <p>The value should be set in range[0, 10k].
          */
         @NonNull
-        public SearchSpec.Builder setSnippetCount(int snippetCount) {
+        public SearchSpec.Builder setSnippetCount(
+                @IntRange(from = 0, to = MAX_SNIPPET_COUNT) int snippetCount) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
             mBundle.putInt(SNIPPET_COUNT_FIELD, snippetCount);
@@ -341,39 +345,40 @@
 
         /**
          * Sets {@code snippetCountPerProperty}. Only the first {@code snippetCountPerProperty}
-         * snippets for a every property of {@link GenericDocument} will contain snippet
-         * information.
+         * snippets for each property of {@link GenericDocument} will contain snippet information.
          *
-         * <p>If set to 0, snippeting is disabled and {@link SearchResult#getMatches}
-         * will return {@code null} for that result.
-         *
-         * <p>The value should be set in range[0, 10k].
+         * <p>If set to 0, snippeting is disabled and {@link SearchResult#getMatches} will return
+         * {@code null} for that result.
          */
         @NonNull
-        public SearchSpec.Builder setSnippetCountPerProperty(int snippetCountPerProperty) {
+        public SearchSpec.Builder setSnippetCountPerProperty(
+                @IntRange(from = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT)
+                        int snippetCountPerProperty) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentInRange(snippetCountPerProperty,
-                    0, MAX_SNIPPET_PER_PROPERTY_COUNT, "snippetCountPerProperty");
+            Preconditions.checkArgumentInRange(
+                    snippetCountPerProperty,
+                    0,
+                    MAX_SNIPPET_PER_PROPERTY_COUNT,
+                    "snippetCountPerProperty");
             mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, snippetCountPerProperty);
             return this;
         }
 
         /**
-         * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at
-         * {@code maxSnippetSize/2} bytes before the middle of the matching token and end at
-         * {@code maxSnippetSize/2} bytes after the middle of the matching token. It respects
-         * token boundaries, therefore the returned window may be smaller than requested.
+         * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at {@code
+         * maxSnippetSize/2} bytes before the middle of the matching token and end at {@code
+         * maxSnippetSize/2} bytes after the middle of the matching token. It respects token
+         * boundaries, therefore the returned window may be smaller than requested.
          *
-         * <p> Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will
-         * be returned. If matches enabled is also set to false, then snippeting is disabled.
+         * <p>Setting {@code maxSnippetSize} to 0 will disable windowing and an empty string will be
+         * returned. If matches enabled is also set to false, then snippeting is disabled.
          *
          * <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will
          * return a window of "bar baz bat" which is only 11 bytes long.
-         *
-         * <p>The value should be in range[0, 10k].
          */
         @NonNull
-        public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
+        public SearchSpec.Builder setMaxSnippetSize(
+                @IntRange(from = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(
                     maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
index f2c8156..3e472fd 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SetSchemaRequest.java
@@ -16,11 +16,11 @@
 
 package android.app.appsearch;
 
-import android.annotation.SuppressLint;
-
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.util.ArraySet;
+
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
@@ -30,9 +30,9 @@
 import java.util.Set;
 
 /**
- * Encapsulates a request to update the schema of an {@link AppSearchManager} database.
+ * Encapsulates a request to update the schema of an {@link AppSearchSession} database.
  *
- * @see AppSearchManager#setSchema
+ * @see AppSearchSession#setSchema
  * @hide
  */
 public final class SetSchemaRequest {
@@ -81,10 +81,10 @@
          * Configures the {@link SetSchemaRequest} to delete any existing documents that don't
          * follow the new schema.
          *
-         * <p>By default, this is {@code false} and schema incompatibility causes the
-         * {@link AppSearchManager#setSchema} call to fail.
+         * <p>By default, this is {@code false} and schema incompatibility causes the {@link
+         * AppSearchSession#setSchema} call to fail.
          *
-         * @see AppSearchManager#setSchema
+         * @see AppSearchSession#setSchema
          */
         @NonNull
         public Builder setForceOverride(boolean forceOverride) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
index 15d0992..704f180 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
@@ -21,18 +21,19 @@
 import android.app.appsearch.AppSearchResult;
 
 /**
- * An exception thrown by {@code android.app.appsearch.AppSearchManager} or a subcomponent.
+ * An exception thrown by {@link android.app.appsearch.AppSearchSession} or a subcomponent.
  *
- * <p>These exceptions can be converted into a failed {@link AppSearchResult}
- * for propagating to the client.
+ * <p>These exceptions can be converted into a failed {@link AppSearchResult} for propagating to the
+ * client.
+ *
  * @hide
  */
-//TODO(b/157082794): Linkify to AppSearchManager once that API is public
 public class AppSearchException extends Exception {
     private final @AppSearchResult.ResultCode int mResultCode;
 
     /**
      * Initializes an {@link AppSearchException} with no message.
+     *
      * @hide
      */
     public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
@@ -59,9 +60,7 @@
         return mResultCode;
     }
 
-    /**
-     * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult}
-     */
+    /** Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */
     @NonNull
     public <T> AppSearchResult<T> toAppSearchResult() {
         return AppSearchResult.newFailedResult(mResultCode, getMessage());
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
index 6dd86f5..5f8da7f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
@@ -18,14 +18,12 @@
 
 import android.annotation.NonNull;
 
-
 /**
  * Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
  * as unpopulated mandatory fields or illegal combinations of parameters.
  *
  * @hide
  */
-
 public class IllegalSchemaException extends IllegalArgumentException {
     /**
      * Constructs a new {@link IllegalSchemaException}.
diff --git a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
index 3ef887f..0b5dc2e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
@@ -18,14 +18,12 @@
 
 import android.annotation.NonNull;
 
-
 /**
- * Indicates that a {@link android.app.appsearch.SearchResult} has logical inconsistencies such
- * as unpopulated mandatory fields or illegal combinations of parameters.
+ * Indicates that a {@link android.app.appsearch.SearchResult} has logical inconsistencies such as
+ * unpopulated mandatory fields or illegal combinations of parameters.
  *
  * @hide
  */
-
 public class IllegalSearchSpecException extends IllegalArgumentException {
     /**
      * Constructs a new {@link IllegalSearchSpecException}.
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 e0215449..247089b 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
@@ -16,13 +16,7 @@
 
 package com.android.server.appsearch.external.localstorage;
 
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
 import android.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
 import android.annotation.WorkerThread;
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
@@ -30,6 +24,12 @@
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
@@ -62,7 +62,6 @@
 import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,55 +76,65 @@
  *
  * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents
  * are physically saved together in {@link IcingSearchEngine}, but logically isolated:
+ *
  * <ul>
- *      <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into
- *          SchemaTypes set in {@link #setSchema}.
- *      <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and
- *          save to namespaces set in {@link #putDocument}.
- *      <li>Remove database name prefix when retrieve documents in {@link #getDocument} and
- *          {@link #query}.
- *      <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of
- *          the queried database when user using empty filters in {@link #query}.
+ *   <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into SchemaTypes
+ *       set in {@link #setSchema}.
+ *   <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and save
+ *       to namespaces set in {@link #putDocument}.
+ *   <li>Remove database name prefix when retrieve documents in {@link #getDocument} and {@link
+ *       #query}.
+ *   <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of the
+ *       queried database when user using empty filters in {@link #query}.
  * </ul>
  *
  * <p>Methods in this class belong to two groups, the query group and the mutate group.
+ *
  * <ul>
- *     <li>All methods are going to modify global parameters and data in Icing are executed under
- *         WRITE lock to keep thread safety.
- *     <li>All methods are going to access global parameters or query data from Icing are executed
- *         under READ lock to improve query performance.
+ *   <li>All methods are going to modify global parameters and data in Icing are executed under
+ *       WRITE lock to keep thread safety.
+ *   <li>All methods are going to access global parameters or query data from Icing are executed
+ *       under READ lock to improve query performance.
  * </ul>
  *
  * <p>This class is thread safe.
  *
  * @hide
  */
-
 @WorkerThread
 public final class AppSearchImpl {
     private static final String TAG = "AppSearchImpl";
-    private static final char DATABASE_DELIMITER = '/';
 
-    @VisibleForTesting
-    static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
-    @VisibleForTesting
-    static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
-    @VisibleForTesting
-    static final int CHECK_OPTIMIZE_INTERVAL = 100;
+    @VisibleForTesting static final char DATABASE_DELIMITER = '/';
+
+    @VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
+    @VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
+    @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
 
     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
-    private final IcingSearchEngine mIcingSearchEngine;
+
+    @GuardedBy("mReadWriteLock")
+    private final IcingSearchEngine mIcingSearchEngineLocked;
+
+    @GuardedBy("mReadWriteLock")
+    private final VisibilityStore mVisibilityStoreLocked;
 
     // The map contains schemaTypes and namespaces for all database. All values in the map have
-    // been already added database name prefix.
-    private final Map<String, Set<String>> mSchemaMap = new HashMap<>();
-    private final Map<String, Set<String>> mNamespaceMap = new HashMap<>();
+    // the database name prefix.
+    // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+    @GuardedBy("mReadWriteLock")
+    private final Map<String, Set<String>> mSchemaMapLocked = new HashMap<>();
+
+    // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+    @GuardedBy("mReadWriteLock")
+    private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
 
     /**
-     * The counter to check when to call {@link #checkForOptimize(boolean)}. The interval is
+     * The counter to check when to call {@link #checkForOptimizeLocked(boolean)}. The interval is
      * {@link #CHECK_OPTIMIZE_INTERVAL}.
      */
-    private int mOptimizeIntervalCount = 0;
+    @GuardedBy("mReadWriteLock")
+    private int mOptimizeIntervalCountLocked = 0;
 
     /**
      * Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given
@@ -134,44 +143,77 @@
     @NonNull
     public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException {
         Preconditions.checkNotNull(icingDir);
-        return new AppSearchImpl(icingDir);
+        AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir);
+        appSearchImpl.initializeVisibilityStore();
+        return appSearchImpl;
     }
 
     private AppSearchImpl(@NonNull File icingDir) throws AppSearchException {
         boolean isReset = false;
         mReadWriteLock.writeLock().lock();
+
         try {
             // We synchronize here because we don't want to call IcingSearchEngine.initialize() more
             // than once. It's unnecessary and can be a costly operation.
-            IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder()
-                    .setBaseDir(icingDir.getAbsolutePath()).build();
-            mIcingSearchEngine = new IcingSearchEngine(options);
+            IcingSearchEngineOptions options =
+                    IcingSearchEngineOptions.newBuilder()
+                            .setBaseDir(icingDir.getAbsolutePath())
+                            .build();
+            mIcingSearchEngineLocked = new IcingSearchEngine(options);
 
-            InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize();
+            InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
             SchemaProto schemaProto = null;
             GetAllNamespacesResultProto getAllNamespacesResultProto = null;
             try {
                 checkSuccess(initializeResultProto.getStatus());
-                schemaProto = getSchemaProto();
-                getAllNamespacesResultProto = mIcingSearchEngine.getAllNamespaces();
+                schemaProto = getSchemaProtoLocked();
+                getAllNamespacesResultProto = mIcingSearchEngineLocked.getAllNamespaces();
                 checkSuccess(getAllNamespacesResultProto.getStatus());
             } catch (AppSearchException e) {
+                Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
                 // Some error. Reset and see if it fixes it.
                 reset();
                 isReset = true;
             }
+
+            // Populate schema map
             for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
                 String qualifiedSchemaType = schema.getSchemaType();
-                addToMap(mSchemaMap, getDatabaseName(qualifiedSchemaType), qualifiedSchemaType);
+                addToMap(
+                        mSchemaMapLocked,
+                        getDatabaseName(qualifiedSchemaType),
+                        qualifiedSchemaType);
             }
+
+            // Populate namespace map
             for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
-                addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace);
+                addToMap(
+                        mNamespaceMapLocked,
+                        getDatabaseName(qualifiedNamespace),
+                        qualifiedNamespace);
             }
+
             // TODO(b/155939114): It's possible to optimize after init, which would reduce the time
             //   to when we're able to serve queries. Consider moving this optimize call out.
             if (!isReset) {
-                checkForOptimize(/* force= */ true);
+                checkForOptimizeLocked(/* force= */ true);
             }
+
+            mVisibilityStoreLocked = new VisibilityStore(this);
+        } finally {
+            mReadWriteLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Initialize the visibility store in AppSearchImpl.
+     *
+     * @throws AppSearchException on IcingSearchEngine error.
+     */
+    void initializeVisibilityStore() throws AppSearchException {
+        mReadWriteLock.writeLock().lock();
+        try {
+            mVisibilityStoreLocked.initialize();
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -182,35 +224,36 @@
      *
      * <p>This method belongs to mutate group.
      *
-     * @param databaseName  The name of the database where this schema lives.
-     * @param schemas       Schemas to set for this app.
+     * @param databaseName The name of the database where this schema lives.
+     * @param schemas Schemas to set for this app.
      * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
-     *                      which do not comply with the new schema will be deleted.
+     *     which do not comply with the new schema will be deleted.
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void setSchema(@NonNull String databaseName, @NonNull Set<AppSearchSchema> schemas,
-            boolean forceOverride) throws AppSearchException {
-        SchemaProto schemaProto = getSchemaProto();
-
-        SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder();
-
-        SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
-        for (AppSearchSchema schema : schemas) {
-            SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
-            newSchemaBuilder.addTypes(schemaTypeProto);
-        }
-
-        // Combine the existing schema (which may have types from other databases) with this
-        // database's new schema. Modifies the existingSchemaBuilder.
-        Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder,
-                newSchemaBuilder.build());
-
-        SetSchemaResultProto setSchemaResultProto;
+    public void setSchema(
+            @NonNull String databaseName,
+            @NonNull Set<AppSearchSchema> schemas,
+            boolean forceOverride)
+            throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
+            SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
+
+            SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
+            for (AppSearchSchema schema : schemas) {
+                SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
+                newSchemaBuilder.addTypes(schemaTypeProto);
+            }
+
+            // Combine the existing schema (which may have types from other databases) with this
+            // database's new schema. Modifies the existingSchemaBuilder.
+            RewrittenSchemaResults rewrittenSchemaResults =
+                    rewriteSchema(databaseName, existingSchemaBuilder, newSchemaBuilder.build());
+
             // Apply schema
-            setSchemaResultProto =
-                    mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride);
+            SetSchemaResultProto setSchemaResultProto =
+                    mIcingSearchEngineLocked.setSchema(
+                            existingSchemaBuilder.build(), forceOverride);
 
             // Determine whether it succeeded.
             try {
@@ -219,11 +262,12 @@
                 // Improve the error message by merging in information about incompatible types.
                 if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
                         || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
-                    String newMessage = e.getMessage()
-                            + "\n  Deleted types: "
-                            + setSchemaResultProto.getDeletedSchemaTypesList()
-                            + "\n  Incompatible types: "
-                            + setSchemaResultProto.getIncompatibleSchemaTypesList();
+                    String newMessage =
+                            e.getMessage()
+                                    + "\n  Deleted types: "
+                                    + setSchemaResultProto.getDeletedSchemaTypesList()
+                                    + "\n  Incompatible types: "
+                                    + setSchemaResultProto.getIncompatibleSchemaTypesList();
                     throw new AppSearchException(e.getResultCode(), newMessage, e.getCause());
                 } else {
                     throw e;
@@ -231,16 +275,18 @@
             }
 
             // Update derived data structures.
-            mSchemaMap.put(databaseName, newTypeNames);
+            mSchemaMapLocked.put(databaseName, rewrittenSchemaResults.mRewrittenQualifiedTypes);
+            mVisibilityStoreLocked.updateSchemas(
+                    databaseName, rewrittenSchemaResults.mDeletedQualifiedTypes);
 
             // Determine whether to schedule an immediate optimize.
             if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
                     || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
-                    && forceOverride)) {
+                            && forceOverride)) {
                 // Any existing schemas which is not in 'schemas' will be deleted, and all
                 // documents of these types were also deleted. And so well if we force override
                 // incompatible schemas.
-                checkForOptimize(/* force= */true);
+                checkForOptimizeLocked(/* force= */ true);
             }
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -248,28 +294,63 @@
     }
 
     /**
+     * Update the visibility settings for this app.
+     *
+     * <p>This method belongs to the mutate group
+     *
+     * @param databaseName The name of the database where the visibility settings will apply.
+     * @param schemasHiddenFromPlatformSurfaces Schemas that should be hidden from platform surfaces
+     * @throws AppSearchException on IcingSearchEngine error
+     */
+    public void setVisibility(
+            @NonNull String databaseName, @NonNull Set<String> schemasHiddenFromPlatformSurfaces)
+            throws AppSearchException {
+        mReadWriteLock.writeLock().lock();
+        try {
+            String databasePrefix = getDatabasePrefix(databaseName);
+            Set<String> qualifiedSchemasHiddenFromPlatformSurface =
+                    new ArraySet<>(schemasHiddenFromPlatformSurfaces.size());
+            for (String schema : schemasHiddenFromPlatformSurfaces) {
+                Set<String> existingSchemas = mSchemaMapLocked.get(databaseName);
+                if (existingSchemas == null || !existingSchemas.contains(databasePrefix + schema)) {
+                    throw new AppSearchException(
+                            AppSearchResult.RESULT_NOT_FOUND,
+                            "Unknown schema(s): "
+                                    + schemasHiddenFromPlatformSurfaces
+                                    + " provided during setVisibility.");
+                }
+                qualifiedSchemasHiddenFromPlatformSurface.add(databasePrefix + schema);
+            }
+            mVisibilityStoreLocked.setVisibility(
+                    databaseName, qualifiedSchemasHiddenFromPlatformSurface);
+        } finally {
+            mReadWriteLock.writeLock().lock();
+        }
+    }
+
+    /**
      * Adds a document to the AppSearch index.
      *
      * <p>This method belongs to mutate group.
      *
      * @param databaseName The databaseName this document resides in.
-     * @param document     The document to index.
+     * @param document The document to index.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     public void putDocument(@NonNull String databaseName, @NonNull GenericDocument document)
             throws AppSearchException {
-        DocumentProto.Builder documentBuilder = GenericDocumentToProtoConverter.convert(
-                document).toBuilder();
+        DocumentProto.Builder documentBuilder =
+                GenericDocumentToProtoConverter.convert(document).toBuilder();
         addPrefixToDocument(documentBuilder, getDatabasePrefix(databaseName));
 
         PutResultProto putResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            putResultProto = mIcingSearchEngine.put(documentBuilder.build());
-            addToMap(mNamespaceMap, databaseName, documentBuilder.getNamespace());
+            putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
+            addToMap(mNamespaceMapLocked, databaseName, documentBuilder.getNamespace());
             // The existing documents with same URI will be deleted, so there maybe some resources
             // could be released after optimize().
-            checkForOptimize(/* force= */false);
+            checkForOptimizeLocked(/* force= */ false);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -282,19 +363,20 @@
      * <p>This method belongs to query group.
      *
      * @param databaseName The databaseName this document resides in.
-     * @param namespace    The namespace this document resides in.
-     * @param uri          The URI of the document to get.
+     * @param namespace The namespace this document resides in.
+     * @param uri The URI of the document to get.
      * @return The Document contents
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public GenericDocument getDocument(@NonNull String databaseName, @NonNull String namespace,
-            @NonNull String uri) throws AppSearchException {
+    public GenericDocument getDocument(
+            @NonNull String databaseName, @NonNull String namespace, @NonNull String uri)
+            throws AppSearchException {
         GetResultProto getResultProto;
         mReadWriteLock.readLock().lock();
         try {
-            getResultProto = mIcingSearchEngine.get(
-                    getDatabasePrefix(databaseName) + namespace, uri);
+            getResultProto =
+                    mIcingSearchEngineLocked.get(getDatabasePrefix(databaseName) + namespace, uri);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -310,19 +392,25 @@
      *
      * <p>This method belongs to query group.
      *
-     * @param databaseName    The databaseName this query for.
+     * @param databaseName The databaseName this query for.
      * @param queryExpression Query String to search.
-     * @param searchSpec      Spec for setting filters, raw query etc.
-     * @return The results of performing this search. It may contain an empty list of results if
-     * no documents matched the query.
+     * @param searchSpec Spec for setting filters, raw query etc.
+     * @return The results of performing this search. It may contain an empty list of results if no
+     *     documents matched the query.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
     public SearchResultPage query(
             @NonNull String databaseName,
             @NonNull String queryExpression,
-            @NonNull SearchSpec searchSpec) throws AppSearchException {
-        return doQuery(Collections.singleton(databaseName), queryExpression, searchSpec);
+            @NonNull SearchSpec searchSpec)
+            throws AppSearchException {
+        mReadWriteLock.readLock().lock();
+        try {
+            return doQueryLocked(Collections.singleton(databaseName), queryExpression, searchSpec);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
     }
 
     /**
@@ -332,45 +420,54 @@
      * <p>This method belongs to query group.
      *
      * @param queryExpression Query String to search.
-     * @param searchSpec      Spec for setting filters, raw query etc.
-     * @return The results of performing this search. It may contain an empty list of results if
-     * no documents matched the query.
+     * @param searchSpec Spec for setting filters, raw query etc.
+     * @return The results of performing this search. It may contain an empty list of results if no
+     *     documents matched the query.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
     public SearchResultPage globalQuery(
-            @NonNull String queryExpression,
-            @NonNull SearchSpec searchSpec) throws AppSearchException {
-        return doQuery(mNamespaceMap.keySet(), queryExpression, searchSpec);
+            @NonNull String queryExpression, @NonNull SearchSpec searchSpec)
+            throws AppSearchException {
+        // TODO(b/169883602): Check if the platform is querying us at a higher level. At this
+        //  point, we should add all platform-surfaceable schemas assuming the querier has been
+        //  verified.
+        mReadWriteLock.readLock().lock();
+        try {
+            // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
+            // that could exist.
+            return doQueryLocked(mNamespaceMapLocked.keySet(), queryExpression, searchSpec);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
     }
 
-    private SearchResultPage doQuery(
-            @NonNull Set<String> databases, @NonNull String queryExpression,
+    @GuardedBy("mReadWriteLock")
+    private SearchResultPage doQueryLocked(
+            @NonNull Set<String> databases,
+            @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        SearchSpecProto searchSpecProto =
-                SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
-        SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
-                .setQuery(queryExpression);
+        SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+        SearchSpecProto.Builder searchSpecBuilder =
+                searchSpecProto.toBuilder().setQuery(queryExpression);
 
         ResultSpecProto resultSpec = SearchSpecToProtoConverter.toResultSpecProto(searchSpec);
         ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
         SearchResultProto searchResultProto;
-        mReadWriteLock.readLock().lock();
-        try {
-            // rewriteSearchSpecForDatabases will return false if none of the databases have
-            // documents, so we can return an empty SearchResult and skip sending request to Icing.
-            // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
-            // that could exist.
-            if (!rewriteSearchSpecForDatabases(searchSpecBuilder, databases)) {
-                return new SearchResultPage(Bundle.EMPTY);
-            }
-            searchResultProto = mIcingSearchEngine.search(
-                    searchSpecBuilder.build(), scoringSpec, resultSpec);
-        } finally {
-            mReadWriteLock.readLock().unlock();
+
+        // rewriteSearchSpecForDatabases will return false if none of the databases that the
+        // client is trying to search on exist, so we can return an empty SearchResult and skip
+        // sending request to Icing.
+        // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
+        // that could exist.
+        if (!rewriteSearchSpecForDatabasesLocked(searchSpecBuilder, databases)) {
+            return new SearchResultPage(Bundle.EMPTY);
         }
+        searchResultProto =
+                mIcingSearchEngineLocked.search(searchSpecBuilder.build(), scoringSpec, resultSpec);
         checkSuccess(searchResultProto.getStatus());
+
         return rewriteSearchResultProto(searchResultProto);
     }
 
@@ -385,11 +482,16 @@
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public SearchResultPage getNextPage(long nextPageToken)
-            throws AppSearchException {
-        SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken);
-        checkSuccess(searchResultProto.getStatus());
-        return rewriteSearchResultProto(searchResultProto);
+    public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
+        mReadWriteLock.readLock().lock();
+        try {
+            SearchResultProto searchResultProto =
+                    mIcingSearchEngineLocked.getNextPage(nextPageToken);
+            checkSuccess(searchResultProto.getStatus());
+            return rewriteSearchResultProto(searchResultProto);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
     }
 
     /**
@@ -398,10 +500,15 @@
      * <p>This method belongs to query group.
      *
      * @param nextPageToken The token of pre-loaded results of previously executed query to be
-     *                      Invalidated.
+     *     Invalidated.
      */
     public void invalidateNextPageToken(long nextPageToken) {
-        mIcingSearchEngine.invalidateNextPageToken(nextPageToken);
+        mReadWriteLock.readLock().lock();
+        try {
+            mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
+        } finally {
+            mReadWriteLock.readLock().unlock();
+        }
     }
 
     /**
@@ -410,18 +517,18 @@
      * <p>This method belongs to mutate group.
      *
      * @param databaseName The databaseName the document is in.
-     * @param namespace    Namespace of the document to remove.
-     * @param uri          URI of the document to remove.
+     * @param namespace Namespace of the document to remove.
+     * @param uri URI of the document to remove.
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void remove(@NonNull String databaseName, @NonNull String namespace,
-            @NonNull String uri) throws AppSearchException {
+    public void remove(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri)
+            throws AppSearchException {
         String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
         DeleteResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            deleteResultProto = mIcingSearchEngine.delete(qualifiedNamespace, uri);
-            checkForOptimize(/* force= */false);
+            deleteResultProto = mIcingSearchEngineLocked.delete(qualifiedNamespace, uri);
+            checkForOptimizeLocked(/* force= */ false);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -433,39 +540,38 @@
      *
      * <p>This method belongs to mutate group.
      *
-     * @param databaseName    The databaseName the document is in.
+     * @param databaseName The databaseName the document is in.
      * @param queryExpression Query String to search.
-     * @param searchSpec      Defines what and how to remove
+     * @param searchSpec Defines what and how to remove
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void removeByQuery(@NonNull String databaseName, @NonNull String queryExpression,
+    public void removeByQuery(
+            @NonNull String databaseName,
+            @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-
-        SearchSpecProto searchSpecProto =
-                SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
-        SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
-                .setQuery(queryExpression);
+        SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+        SearchSpecProto.Builder searchSpecBuilder =
+                searchSpecProto.toBuilder().setQuery(queryExpression);
         DeleteResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
             // Only rewrite SearchSpec for non empty database.
             // rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
             // should skip sending request to Icing and return in here.
-            if (!rewriteSearchSpecForDatabases(searchSpecBuilder,
-                    Collections.singleton(databaseName))) {
+            if (!rewriteSearchSpecForDatabasesLocked(
+                    searchSpecBuilder, Collections.singleton(databaseName))) {
                 return;
             }
-            deleteResultProto = mIcingSearchEngine.deleteByQuery(
-                    searchSpecBuilder.build());
-            checkForOptimize(/* force= */true);
+            deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
+            checkForOptimizeLocked(/* force= */ true);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
         // It seems that the caller wants to get success if the data matching the query is not in
         // the DB because it was not there or was successfully deleted.
-        checkCodeOneOf(deleteResultProto.getStatus(),
-                StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
+        checkCodeOneOf(
+                deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
     }
 
     /**
@@ -475,36 +581,53 @@
      *
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    @VisibleForTesting
-    public void reset() throws AppSearchException {
+    private void reset() throws AppSearchException {
         ResetResultProto resetResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            resetResultProto = mIcingSearchEngine.reset();
-            mOptimizeIntervalCount = 0;
-            mSchemaMap.clear();
-            mNamespaceMap.clear();
+            resetResultProto = mIcingSearchEngineLocked.reset();
+            mOptimizeIntervalCountLocked = 0;
+            mSchemaMapLocked.clear();
+            mNamespaceMapLocked.clear();
+
+            // Must be called after everything else since VisibilityStore may repopulate
+            // IcingSearchEngine with an initial schema.
+            mVisibilityStoreLocked.handleReset();
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
         checkSuccess(resetResultProto.getStatus());
     }
 
+    /** Wrapper around schema changes */
+    @VisibleForTesting
+    static class RewrittenSchemaResults {
+        // Any database-qualified types that used to exist in the schema, but are deleted in the
+        // new one.
+        final Set<String> mDeletedQualifiedTypes = new ArraySet<>();
+
+        // Database-qualified types that were part of the new schema.
+        final Set<String> mRewrittenQualifiedTypes = new ArraySet<>();
+    }
+
     /**
      * Rewrites all types mentioned in the given {@code newSchema} to prepend {@code prefix}.
      * Rewritten types will be added to the {@code existingSchema}.
      *
-     * @param databaseName   The name of the database where this schema lives.
+     * @param databaseName The name of the database where this schema lives.
      * @param existingSchema A schema that may contain existing types from across all database
-     *                       instances. Will be mutated to contain the properly rewritten schema
-     *                       types from {@code newSchema}.
-     * @param newSchema      Schema with types to add to the {@code existingSchema}.
-     * @return a Set contains all remaining qualified schema type names in given database.
+     *     instances. Will be mutated to contain the properly rewritten schema types from {@code
+     *     newSchema}.
+     * @param newSchema Schema with types to add to the {@code existingSchema}.
+     * @return a RewrittenSchemaResults contains all qualified schema type names in the given
+     *     database as well as a set of schema types that were deleted from the database.
      */
     @VisibleForTesting
-    Set<String> rewriteSchema(@NonNull String databaseName,
+    static RewrittenSchemaResults rewriteSchema(
+            @NonNull String databaseName,
             @NonNull SchemaProto.Builder existingSchema,
-            @NonNull SchemaProto newSchema) throws AppSearchException {
+            @NonNull SchemaProto newSchema)
+            throws AppSearchException {
         String prefix = getDatabasePrefix(databaseName);
         HashMap<String, SchemaTypeConfigProto> newTypesToProto = new HashMap<>();
         // Rewrite the schema type to include the typePrefix.
@@ -523,8 +646,7 @@
                 PropertyConfigProto.Builder propertyConfigBuilder =
                         typeConfigBuilder.getProperties(propertyIdx).toBuilder();
                 if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
-                    String newPropertySchemaType =
-                            prefix + propertyConfigBuilder.getSchemaType();
+                    String newPropertySchemaType = prefix + propertyConfigBuilder.getSchemaType();
                     propertyConfigBuilder.setSchemaType(newPropertySchemaType);
                     typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
                 }
@@ -533,7 +655,9 @@
             newTypesToProto.put(newSchemaType, typeConfigBuilder.build());
         }
 
-        Set<String> newSchemaTypesName = newTypesToProto.keySet();
+        // newTypesToProto is modified below, so we need a copy first
+        RewrittenSchemaResults rewrittenSchemaResults = new RewrittenSchemaResults();
+        rewrittenSchemaResults.mRewrittenQualifiedTypes.addAll(newTypesToProto.keySet());
 
         // Combine the existing schema (which may have types from other databases) with this
         // database's new schema. Modifies the existingSchemaBuilder.
@@ -548,26 +672,26 @@
                 // All types existing before but not in newSchema should be removed.
                 existingSchema.removeTypes(i);
                 --i;
+                rewrittenSchemaResults.mDeletedQualifiedTypes.add(schemaType);
             }
         }
         // We've been removing existing types from newTypesToProto, so everything that remains is
         // new.
         existingSchema.addAllTypes(newTypesToProto.values());
 
-        return newSchemaTypesName;
+        return rewrittenSchemaResults;
     }
 
     /**
-     * Prepends {@code prefix} to all types and namespaces mentioned anywhere in
-     * {@code documentBuilder}.
+     * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
+     * documentBuilder}.
      *
      * @param documentBuilder The document to mutate
-     * @param prefix          The prefix to add
+     * @param prefix The prefix to add
      */
     @VisibleForTesting
-    void addPrefixToDocument(
-            @NonNull DocumentProto.Builder documentBuilder,
-            @NonNull String prefix) {
+    static void addPrefixToDocument(
+            @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
         // Rewrite the type name to include/remove the prefix.
         String newSchema = prefix + documentBuilder.getSchema();
         documentBuilder.setSchema(newSchema);
@@ -595,27 +719,17 @@
     }
 
     /**
-     * Removes any database names from types and namespaces mentioned anywhere in
-     * {@code documentBuilder}.
+     * Removes any database names from types and namespaces mentioned anywhere in {@code
+     * documentBuilder}.
      *
      * @param documentBuilder The document to mutate
      */
     @VisibleForTesting
-    void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder) {
-        int delimiterIndex;
-        if ((delimiterIndex = documentBuilder.getSchema().indexOf(DATABASE_DELIMITER)) != -1) {
-            // Rewrite the type name to remove the prefix.
-            // Add 1 to include the char size of the DATABASE_DELIMITER
-            String newSchema = documentBuilder.getSchema().substring(delimiterIndex + 1);
-            documentBuilder.setSchema(newSchema);
-        }
-
-        if ((delimiterIndex = documentBuilder.getNamespace().indexOf(DATABASE_DELIMITER)) != -1) {
-            // Rewrite the namespace to remove the prefix.
-            // Add 1 to include the char size of the DATABASE_DELIMITER
-            String newNamespace = documentBuilder.getNamespace().substring(delimiterIndex + 1);
-            documentBuilder.setNamespace(newNamespace);
-        }
+    static void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+            throws AppSearchException {
+        // Rewrite the type name and namespace to remove the prefix.
+        documentBuilder.setSchema(removeDatabasePrefix(documentBuilder.getSchema()));
+        documentBuilder.setNamespace(removeDatabasePrefix(documentBuilder.getNamespace()));
 
         // Recurse into derived documents
         for (int propertyIdx = 0;
@@ -639,8 +753,9 @@
     /**
      * Rewrites the schemaTypeFilters and namespacesFilters that exist in {@code databaseNames}.
      *
-     * <p>If the searchSpec has empty filter lists, all existing databases from
-     * {@code databaseNames} will be added.
+     * <p>If the searchSpec has empty filter lists, all existing databases from {@code
+     * databaseNames} will be added.
+     *
      * <p>This method should be only called in query methods and get the READ lock to keep thread
      * safety.
      *
@@ -648,11 +763,11 @@
      */
     @VisibleForTesting
     @GuardedBy("mReadWriteLock")
-    boolean rewriteSearchSpecForDatabases(
+    boolean rewriteSearchSpecForDatabasesLocked(
             @NonNull SearchSpecProto.Builder searchSpecBuilder,
             @NonNull Set<String> databaseNames) {
         // Create a copy since retainAll() modifies the original set.
-        Set<String> existingDatabases = new HashSet<>(mNamespaceMap.keySet());
+        Set<String> existingDatabases = new ArraySet<>(mNamespaceMapLocked.keySet());
         existingDatabases.retainAll(databaseNames);
 
         if (existingDatabases.isEmpty()) {
@@ -669,29 +784,29 @@
 
         // Rewrite filters to include a database prefix.
         for (String databaseName : existingDatabases) {
-            Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
+            Set<String> existingSchemaTypes = mSchemaMapLocked.get(databaseName);
+            String databaseNamePrefix = getDatabasePrefix(databaseName);
             if (schemaTypeFilters.isEmpty()) {
                 // Include all schema types
                 searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
             } else {
                 // Qualify the given schema types
-                for (String schemaType : schemaTypeFilters) {
-                    String qualifiedType = getDatabasePrefix(databaseName) + schemaType;
+                for (int i = 0; i < schemaTypeFilters.size(); i++) {
+                    String qualifiedType = databaseNamePrefix + schemaTypeFilters.get(i);
                     if (existingSchemaTypes.contains(qualifiedType)) {
                         searchSpecBuilder.addSchemaTypeFilters(qualifiedType);
                     }
-
                 }
             }
 
-            Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
+            Set<String> existingNamespaces = mNamespaceMapLocked.get(databaseName);
             if (namespaceFilters.isEmpty()) {
                 // Include all namespaces
                 searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
             } else {
                 // Qualify the given namespaces.
-                for (String namespace : namespaceFilters) {
-                    String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
+                for (int i = 0; i < namespaceFilters.size(); i++) {
+                    String qualifiedNamespace = databaseNamePrefix + namespaceFilters.get(i);
                     if (existingNamespaces.contains(qualifiedNamespace)) {
                         searchSpecBuilder.addNamespaceFilters(qualifiedNamespace);
                     }
@@ -703,35 +818,71 @@
     }
 
     @VisibleForTesting
-    SchemaProto getSchemaProto() throws AppSearchException {
-        GetSchemaResultProto schemaProto = mIcingSearchEngine.getSchema();
+    @GuardedBy("mReadWriteLock")
+    SchemaProto getSchemaProtoLocked() throws AppSearchException {
+        GetSchemaResultProto schemaProto = mIcingSearchEngineLocked.getSchema();
         // TODO(b/161935693) check GetSchemaResultProto is success or not. Call reset() if it's not.
         // TODO(b/161935693) only allow GetSchemaResultProto NOT_FOUND on first run
         checkCodeOneOf(schemaProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
         return schemaProto.getSchema();
     }
 
+    /** Returns true if {@code databaseName} has a {@code schemaType} */
+    @GuardedBy("mReadWriteLock")
+    boolean hasSchemaTypeLocked(@NonNull String databaseName, @NonNull String schemaType) {
+        Preconditions.checkNotNull(databaseName);
+        Preconditions.checkNotNull(schemaType);
+
+        Set<String> schemaTypes = mSchemaMapLocked.get(databaseName);
+        if (schemaTypes == null) {
+            return false;
+        }
+
+        return schemaTypes.contains(getDatabasePrefix(databaseName) + schemaType);
+    }
+
+    /** Returns a set of all databases AppSearchImpl knows about. */
+    @GuardedBy("mReadWriteLock")
     @NonNull
-    private String getDatabasePrefix(@NonNull String databaseName) {
+    Set<String> getDatabasesLocked() {
+        return mSchemaMapLocked.keySet();
+    }
+
+    @NonNull
+    private static String getDatabasePrefix(@NonNull String databaseName) {
         // TODO(b/170370381): Reconsider the way we separate database names for security reasons.
         return databaseName + DATABASE_DELIMITER;
     }
 
     @NonNull
-    private String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
+    private static String removeDatabasePrefix(@NonNull String prefixedString)
+            throws AppSearchException {
+        int delimiterIndex;
+        if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
+            // Add 1 to include the char size of the DATABASE_DELIMITER
+            return prefixedString.substring(delimiterIndex + 1);
+        }
+        throw new AppSearchException(
+                AppSearchResult.RESULT_UNKNOWN_ERROR,
+                "The prefixed value doesn't contains a valid database name.");
+    }
+
+    @NonNull
+    private static String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
         int delimiterIndex = prefixedValue.indexOf(DATABASE_DELIMITER);
         if (delimiterIndex == -1) {
-            throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+            throw new AppSearchException(
+                    AppSearchResult.RESULT_UNKNOWN_ERROR,
                     "The databaseName prefixed value doesn't contains a valid database name.");
         }
         return prefixedValue.substring(0, delimiterIndex);
     }
 
-    @GuardedBy("mReadWriteLock")
-    private void addToMap(Map<String, Set<String>> map, String databaseName, String prefixedValue) {
+    private static void addToMap(
+            Map<String, Set<String>> map, String databaseName, String prefixedValue) {
         Set<String> values = map.get(databaseName);
         if (values == null) {
-            values = new HashSet<>();
+            values = new ArraySet<>();
             map.put(databaseName, values);
         }
         values.add(prefixedValue);
@@ -742,15 +893,15 @@
      *
      * @throws AppSearchException on error codes.
      */
-    private void checkSuccess(StatusProto statusProto) throws AppSearchException {
+    private static void checkSuccess(StatusProto statusProto) throws AppSearchException {
         checkCodeOneOf(statusProto, StatusProto.Code.OK);
     }
 
     /**
-     * Checks the given status code is one of the provided codes, and throws an
-     * {@link AppSearchException} if it is not.
+     * Checks the given status code is one of the provided codes, and throws an {@link
+     * AppSearchException} if it is not.
      */
-    private void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes)
+    private static void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes)
             throws AppSearchException {
         for (int i = 0; i < codes.length; i++) {
             if (codes[i] == statusProto.getCode()) {
@@ -774,27 +925,28 @@
      *
      * <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
      * safety.
-     * <p>{@link IcingSearchEngine#optimize()} should be called only if
-     * {@link GetOptimizeInfoResultProto} shows there is enough resources could be released.
-     * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per
-     * {@link #CHECK_OPTIMIZE_INTERVAL} of remove executions.
+     *
+     * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
+     * GetOptimizeInfoResultProto} shows there is enough resources could be released.
+     *
+     * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per {@link
+     * #CHECK_OPTIMIZE_INTERVAL} of remove executions.
      *
      * @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}.
      */
     @GuardedBy("mReadWriteLock")
-    private void checkForOptimize(boolean force) throws AppSearchException {
-        ++mOptimizeIntervalCount;
-        if (force || mOptimizeIntervalCount >= CHECK_OPTIMIZE_INTERVAL) {
-            mOptimizeIntervalCount = 0;
-            GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResult();
+    private void checkForOptimizeLocked(boolean force) throws AppSearchException {
+        ++mOptimizeIntervalCountLocked;
+        if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
+            mOptimizeIntervalCountLocked = 0;
+            GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
             checkSuccess(optimizeInfo.getStatus());
             // Second threshold, decide when to call optimize().
             if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
-                    || optimizeInfo.getEstimatedOptimizableBytes()
-                    >= OPTIMIZE_THRESHOLD_BYTES) {
+                    || optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) {
                 // TODO(b/155939114): call optimize in the same thread will slow down api calls
                 //  significantly. Move this call to background.
-                OptimizeResultProto optimizeResultProto = mIcingSearchEngine.optimize();
+                OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
                 checkSuccess(optimizeResultProto.getStatus());
             }
             // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
@@ -804,8 +956,8 @@
     }
 
     /** Remove the rewritten schema types from any result documents. */
-    private SearchResultPage rewriteSearchResultProto(
-            @NonNull SearchResultProto searchResultProto) {
+    private static SearchResultPage rewriteSearchResultProto(
+            @NonNull SearchResultProto searchResultProto) throws AppSearchException {
         SearchResultProto.Builder resultsBuilder = searchResultProto.toBuilder();
         for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
             if (searchResultProto.getResults(i).hasDocument()) {
@@ -820,40 +972,48 @@
         return SearchResultToProtoConverter.convertToSearchResultPage(resultsBuilder);
     }
 
+    @GuardedBy("mReadWriteLock")
     @VisibleForTesting
-    GetOptimizeInfoResultProto getOptimizeInfoResult() {
-        return mIcingSearchEngine.getOptimizeInfo();
+    GetOptimizeInfoResultProto getOptimizeInfoResultLocked() {
+        return mIcingSearchEngineLocked.getOptimizeInfo();
+    }
+
+    @GuardedBy("mReadWriteLock")
+    @VisibleForTesting
+    VisibilityStore getVisibilityStoreLocked() {
+        return mVisibilityStoreLocked;
     }
 
     /**
-     * Converts an erroneous status code to an AppSearchException. Callers should ensure that
-     * the status code is not OK or WARNING_DATA_LOSS.
+     * Converts an erroneous status code to an AppSearchException. Callers should ensure that the
+     * status code is not OK or WARNING_DATA_LOSS.
      *
      * @param statusProto StatusProto with error code and message to translate into
-     *                    AppSearchException.
+     *     AppSearchException.
      * @return AppSearchException with the parallel error code.
      */
-    private AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
+    private static AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
         switch (statusProto.getCode()) {
             case INVALID_ARGUMENT:
-                return new AppSearchException(AppSearchResult.RESULT_INVALID_ARGUMENT,
-                        statusProto.getMessage());
+                return new AppSearchException(
+                        AppSearchResult.RESULT_INVALID_ARGUMENT, statusProto.getMessage());
             case NOT_FOUND:
-                return new AppSearchException(AppSearchResult.RESULT_NOT_FOUND,
-                        statusProto.getMessage());
+                return new AppSearchException(
+                        AppSearchResult.RESULT_NOT_FOUND, statusProto.getMessage());
             case FAILED_PRECONDITION:
                 // Fallthrough
             case ABORTED:
                 // Fallthrough
             case INTERNAL:
-                return new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR,
-                        statusProto.getMessage());
+                return new AppSearchException(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, statusProto.getMessage());
             case OUT_OF_SPACE:
-                return new AppSearchException(AppSearchResult.RESULT_OUT_OF_SPACE,
-                        statusProto.getMessage());
+                return new AppSearchException(
+                        AppSearchResult.RESULT_OUT_OF_SPACE, statusProto.getMessage());
             default:
                 // Some unknown/unsupported error
-                return new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+                return new AppSearchException(
+                        AppSearchResult.RESULT_UNKNOWN_ERROR,
                         "Unknown IcingSearchEngine status code: " + statusProto.getCode());
         }
     }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
new file mode 100644
index 0000000..4722822
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/VisibilityStore.java
@@ -0,0 +1,257 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages any visibility settings for all the databases that AppSearchImpl knows about. Persists
+ * the visibility settings and reloads them on initialization.
+ *
+ * <p>The VisibilityStore creates a document for each database. This document holds the visibility
+ * settings that apply to that database. The VisibilityStore also creates a schema for these
+ * documents and has its own database so that its data doesn't interfere with any clients' data. It
+ * persists the document and schema through AppSearchImpl.
+ *
+ * <p>These visibility settings are used to ensure AppSearch queries respect the clients' settings
+ * on who their data is visible to.
+ *
+ * <p>This class doesn't handle any locking itself. Its callers should handle the locking at a
+ * higher level.
+ *
+ * <p>NOTE: This class holds an instance of AppSearchImpl and AppSearchImpl holds an instance of
+ * this class. Take care to not cause any circular dependencies.
+ */
+class VisibilityStore {
+    // Schema type for documents that hold AppSearch's metadata, e.g. visibility settings
+    @VisibleForTesting static final String SCHEMA_TYPE = "Visibility";
+    // Property that holds the list of platform-hidden schemas, as part of the visibility
+    // settings.
+    @VisibleForTesting static final String PLATFORM_HIDDEN_PROPERTY = "platformHidden";
+    // Database name to prefix all visibility schemas and documents with. Special-cased to
+    // minimize the chance of collision with a client-supplied database.
+    @VisibleForTesting static final String DATABASE_NAME = "$$__AppSearch__Database";
+    // Namespace of documents that contain visibility settings
+    private static final String NAMESPACE = "namespace";
+    private final AppSearchImpl mAppSearchImpl;
+
+    // The map contains schemas that are platform-hidden for each database. All schemas in the map
+    // have a database name prefix.
+    private final Map<String, Set<String>> mPlatformHiddenMap = new ArrayMap<>();
+
+    /**
+     * Creates an uninitialized VisibilityStore object. Callers must also call {@link #initialize()}
+     * before using the object.
+     *
+     * @param appSearchImpl AppSearchImpl instance
+     */
+    VisibilityStore(@NonNull AppSearchImpl appSearchImpl) {
+        mAppSearchImpl = appSearchImpl;
+    }
+
+    /**
+     * Initializes schemas and member variables to track visibility settings.
+     *
+     * <p>This is kept separate from the constructor because this will call methods on
+     * AppSearchImpl. Some may even then recursively call back into VisibilityStore (for example,
+     * {@link AppSearchImpl#setSchema} will call {@link #updateSchemas}. We need to have both
+     * AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
+     *
+     * @throws AppSearchException AppSearchException on AppSearchImpl error.
+     */
+    public void initialize() throws AppSearchException {
+        if (!mAppSearchImpl.hasSchemaTypeLocked(DATABASE_NAME, SCHEMA_TYPE)) {
+            // Schema type doesn't exist yet. Add it.
+            mAppSearchImpl.setSchema(
+                    DATABASE_NAME,
+                    Collections.singleton(
+                            new AppSearchSchema.Builder(SCHEMA_TYPE)
+                                    .addProperty(
+                                            new AppSearchSchema.PropertyConfig.Builder(
+                                                            PLATFORM_HIDDEN_PROPERTY)
+                                                    .setDataType(
+                                                            AppSearchSchema.PropertyConfig
+                                                                    .DATA_TYPE_STRING)
+                                                    .setCardinality(
+                                                            AppSearchSchema.PropertyConfig
+                                                                    .CARDINALITY_REPEATED)
+                                                    .build())
+                                    .build()),
+                    /*forceOverride=*/ false);
+        }
+
+        // Populate visibility settings map
+        for (String database : mAppSearchImpl.getDatabasesLocked()) {
+            if (database.equals(DATABASE_NAME)) {
+                // Our own database. Skip
+                continue;
+            }
+
+            try {
+                // Note: We use the other clients' database names as uris
+                GenericDocument document =
+                        mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ database);
+
+                String[] schemas = document.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
+                mPlatformHiddenMap.put(database, new ArraySet<>(Arrays.asList(schemas)));
+            } catch (AppSearchException e) {
+                if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
+                    // TODO(b/172068212): This indicates some desync error. We were expecting a
+                    //  document, but didn't find one. Should probably reset AppSearch instead of
+                    //  ignoring it.
+                    continue;
+                }
+                // Otherwise, this is some other error we should pass up.
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Update visibility settings for the {@code databaseName}.
+     *
+     * @param schemasToRemove Database-prefixed schemas that should be removed
+     */
+    public void updateSchemas(@NonNull String databaseName, @NonNull Set<String> schemasToRemove)
+            throws AppSearchException {
+        Preconditions.checkNotNull(databaseName);
+        Preconditions.checkNotNull(schemasToRemove);
+
+        GenericDocument visibilityDocument;
+        try {
+            visibilityDocument =
+                    mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ databaseName);
+        } catch (AppSearchException e) {
+            if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
+                // This might be the first time we're seeing visibility changes for a database.
+                // Create a new visibility document.
+                mAppSearchImpl.putDocument(
+                        DATABASE_NAME,
+                        new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+                                .setNamespace(NAMESPACE)
+                                .build());
+
+                // Since we know there was nothing that existed before, we don't need to remove
+                // anything either. Return early.
+                return;
+            }
+            // Otherwise, this is some real error we should pass up.
+            throw e;
+        }
+
+        String[] hiddenSchemas =
+                visibilityDocument.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
+        if (hiddenSchemas == null) {
+            // Nothing to remove.
+            return;
+        }
+
+        // Create a new set so we can remove from it.
+        Set<String> remainingSchemas = new ArraySet<>(Arrays.asList(hiddenSchemas));
+        boolean changed = remainingSchemas.removeAll(schemasToRemove);
+        if (!changed) {
+            // Nothing was actually removed. Can return early.
+            return;
+        }
+
+        // Update our persisted document
+        // TODO(b/171882200): Switch to a .toBuilder API when it's available.
+        GenericDocument.Builder newVisibilityDocument =
+                new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+                        .setNamespace(NAMESPACE);
+        if (!remainingSchemas.isEmpty()) {
+            newVisibilityDocument.setPropertyString(
+                    PLATFORM_HIDDEN_PROPERTY, remainingSchemas.toArray(new String[0]));
+        }
+        mAppSearchImpl.putDocument(DATABASE_NAME, newVisibilityDocument.build());
+
+        // Update derived data structures
+        mPlatformHiddenMap.put(databaseName, remainingSchemas);
+    }
+
+    /**
+     * Sets visibility settings for {@code databaseName}. Any previous visibility settings will be
+     * overwritten.
+     *
+     * @param databaseName Database name that owns the {@code platformHiddenSchemas}.
+     * @param platformHiddenSchemas Set of database-qualified schemas that should be hidden from the
+     *     platform.
+     * @throws AppSearchException on AppSearchImpl error.
+     */
+    public void setVisibility(
+            @NonNull String databaseName, @NonNull Set<String> platformHiddenSchemas)
+            throws AppSearchException {
+        Preconditions.checkNotNull(databaseName);
+        Preconditions.checkNotNull(platformHiddenSchemas);
+
+        // Persist the document
+        GenericDocument.Builder visibilityDocument =
+                new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
+                        .setNamespace(NAMESPACE);
+        if (!platformHiddenSchemas.isEmpty()) {
+            visibilityDocument.setPropertyString(
+                    PLATFORM_HIDDEN_PROPERTY, platformHiddenSchemas.toArray(new String[0]));
+        }
+        mAppSearchImpl.putDocument(DATABASE_NAME, visibilityDocument.build());
+
+        // Update derived data structures.
+        mPlatformHiddenMap.put(databaseName, platformHiddenSchemas);
+    }
+
+    /**
+     * Returns the set of database-qualified schemas in {@code databaseName} that are hidden from
+     * the platform.
+     *
+     * @param databaseName Database name to retrieve schemas for
+     * @return Set of database-qualified schemas that are hidden from the platform. Empty set if
+     *     none exist.
+     */
+    @NonNull
+    public Set<String> getPlatformHiddenSchemas(@NonNull String databaseName) {
+        Preconditions.checkNotNull(databaseName);
+        Set<String> platformHiddenSchemas = mPlatformHiddenMap.get(databaseName);
+        if (platformHiddenSchemas == null) {
+            return Collections.emptySet();
+        }
+        return platformHiddenSchemas;
+    }
+
+    /**
+     * Handles an {@link AppSearchImpl#reset()} by clearing any cached state and resetting to a
+     * first-initialized state.
+     *
+     * @throws AppSearchException on AppSearchImpl error.
+     */
+    public void handleReset() throws AppSearchException {
+        mPlatformHiddenMap.clear();
+        initialize();
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index 60684f0..8f4e7ff 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -17,8 +17,8 @@
 package com.android.server.appsearch.external.localstorage.converter;
 
 import android.annotation.NonNull;
-
 import android.app.appsearch.GenericDocument;
+
 import com.android.internal.util.Preconditions;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -30,9 +30,9 @@
 
 /**
  * Translates a {@link GenericDocument} into a {@link DocumentProto}.
+ *
  * @hide
  */
-
 public final class GenericDocumentToProtoConverter {
     private GenericDocumentToProtoConverter() {}
 
@@ -42,7 +42,8 @@
     public static DocumentProto convert(@NonNull GenericDocument document) {
         Preconditions.checkNotNull(document);
         DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
-        mProtoBuilder.setUri(document.getUri())
+        mProtoBuilder
+                .setUri(document.getUri())
                 .setSchema(document.getSchemaType())
                 .setNamespace(document.getNamespace())
                 .setScore(document.getScore())
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
index 403711f..642c2a7 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java
@@ -17,34 +17,34 @@
 package com.android.server.appsearch.external.localstorage.converter;
 
 import android.annotation.NonNull;
-
 import android.app.appsearch.AppSearchSchema;
+
 import com.android.internal.util.Preconditions;
 
-import com.google.android.icing.proto.IndexingConfig;
 import com.google.android.icing.proto.PropertyConfigProto;
 import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.StringIndexingConfig;
 import com.google.android.icing.proto.TermMatchType;
 
 import java.util.List;
 
 /**
  * Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
+ *
  * @hide
  */
-
 public final class SchemaToProtoConverter {
     private SchemaToProtoConverter() {}
 
     /**
-     * Converts an {@link android.app.appsearch.AppSearchSchema} into a
-     * {@link SchemaTypeConfigProto}.
+     * Converts an {@link android.app.appsearch.AppSearchSchema} into a {@link
+     * SchemaTypeConfigProto}.
      */
     @NonNull
     public static SchemaTypeConfigProto convert(@NonNull AppSearchSchema schema) {
         Preconditions.checkNotNull(schema);
         SchemaTypeConfigProto.Builder protoBuilder =
-                SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaTypeName());
+                SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaType());
         List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
         for (int i = 0; i < properties.size(); i++) {
             PropertyConfigProto propertyProto = convertProperty(properties.get(i));
@@ -57,9 +57,9 @@
     private static PropertyConfigProto convertProperty(
             @NonNull AppSearchSchema.PropertyConfig property) {
         Preconditions.checkNotNull(property);
-        PropertyConfigProto.Builder propertyConfigProto = PropertyConfigProto.newBuilder()
-                .setPropertyName(property.getName());
-        IndexingConfig.Builder indexingConfig = IndexingConfig.newBuilder();
+        PropertyConfigProto.Builder propertyConfigProto =
+                PropertyConfigProto.newBuilder().setPropertyName(property.getName());
+        StringIndexingConfig.Builder indexingConfig = StringIndexingConfig.newBuilder();
 
         // Set dataType
         @AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType();
@@ -104,17 +104,17 @@
         indexingConfig.setTermMatchType(termMatchTypeProto);
 
         // Set tokenizerType
-        @AppSearchSchema.PropertyConfig.TokenizerType int tokenizerType =
-                property.getTokenizerType();
-        IndexingConfig.TokenizerType.Code tokenizerTypeProto =
-                IndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
+        @AppSearchSchema.PropertyConfig.TokenizerType
+        int tokenizerType = property.getTokenizerType();
+        StringIndexingConfig.TokenizerType.Code tokenizerTypeProto =
+                StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
         if (tokenizerTypeProto == null) {
             throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
         }
         indexingConfig.setTokenizerType(tokenizerTypeProto);
 
         // Build!
-        propertyConfigProto.setIndexingConfig(indexingConfig);
+        propertyConfigProto.setStringIndexingConfig(indexingConfig);
         return propertyConfigProto.build();
     }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index 4310b42..b91a393 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -16,13 +16,11 @@
 
 package com.android.server.appsearch.external.localstorage.converter;
 
-import android.os.Bundle;
-
 import android.annotation.NonNull;
-
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultPage;
+import android.os.Bundle;
 
 import com.google.android.icing.proto.SearchResultProto;
 import com.google.android.icing.proto.SearchResultProtoOrBuilder;
@@ -36,11 +34,9 @@
  *
  * @hide
  */
-
 public class SearchResultToProtoConverter {
     private SearchResultToProtoConverter() {}
 
-
     /** Translate a {@link SearchResultProto} into {@link SearchResultPage}. */
     @NonNull
     public static SearchResultPage convertToSearchResultPage(
@@ -68,8 +64,9 @@
             for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
                 SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
                 for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
-                    Bundle matchInfoBundle = convertToMatchInfoBundle(
-                            entry.getSnippetMatches(j), entry.getPropertyName());
+                    Bundle matchInfoBundle =
+                            convertToMatchInfoBundle(
+                                    entry.getSnippetMatches(j), entry.getPropertyName());
                     matchList.add(matchInfoBundle);
                 }
             }
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 14822dc..814ee4f 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
@@ -17,8 +17,8 @@
 package com.android.server.appsearch.external.localstorage.converter;
 
 import android.annotation.NonNull;
-
 import android.app.appsearch.SearchSpec;
+
 import com.android.internal.util.Preconditions;
 
 import com.google.android.icing.proto.ResultSpecProto;
@@ -28,9 +28,9 @@
 
 /**
  * Translates a {@link SearchSpec} into icing search protos.
+ *
  * @hide
  */
-
 public final class SearchSpecToProtoConverter {
     private SearchSpecToProtoConverter() {}
 
@@ -38,9 +38,10 @@
     @NonNull
     public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
         Preconditions.checkNotNull(spec);
-        SearchSpecProto.Builder protoBuilder = SearchSpecProto.newBuilder()
-                .addAllSchemaTypeFilters(spec.getSchemas())
-                .addAllNamespaceFilters(spec.getNamespaces());
+        SearchSpecProto.Builder protoBuilder =
+                SearchSpecProto.newBuilder()
+                        .addAllSchemaTypeFilters(spec.getSchemaTypes())
+                        .addAllNamespaceFilters(spec.getNamespaces());
 
         @SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
         TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
@@ -57,7 +58,7 @@
     public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
         Preconditions.checkNotNull(spec);
         return ResultSpecProto.newBuilder()
-                .setNumPerPage(spec.getNumPerPage())
+                .setNumPerPage(spec.getResultCountPerPage())
                 .setSnippetSpec(
                         ResultSpecProto.SnippetSpecProto.newBuilder()
                                 .setNumToSnippet(spec.getSnippetCount())
@@ -84,8 +85,8 @@
         ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
                 ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategyCode);
         if (rankingStrategyCodeProto == null) {
-            throw new IllegalArgumentException("Invalid result ranking strategy: "
-                    + rankingStrategyCode);
+            throw new IllegalArgumentException(
+                    "Invalid result ranking strategy: " + rankingStrategyCode);
         }
         protoBuilder.setRankBy(rankingStrategyCodeProto);
 
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
new file mode 100644
index 0000000..a2bf0d5
--- /dev/null
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -0,0 +1 @@
+I2decd83fab4c4d58fe38c9970f804046479c942c
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index c2d530d..2c8a558 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1506,56 +1506,16 @@
          * @return The job object to hand to the JobScheduler. This object is immutable.
          */
         public JobInfo build() {
-            // Check that network estimates require network type
-            if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) {
-                throw new IllegalArgumentException(
-                        "Can't provide estimated network usage without requiring a network");
-            }
-            // We can't serialize network specifiers
-            if (mIsPersisted && mNetworkRequest != null
-                    && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
-                throw new IllegalArgumentException(
-                        "Network specifiers aren't supported for persistent jobs");
-            }
-            // Check that a deadline was not set on a periodic job.
-            if (mIsPeriodic) {
-                if (mMaxExecutionDelayMillis != 0L) {
-                    throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
-                            "periodic job.");
-                }
-                if (mMinLatencyMillis != 0L) {
-                    throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
-                            "periodic job");
-                }
-                if (mTriggerContentUris != null) {
-                    throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
-                            "periodic job");
-                }
-            }
-            if (mIsPersisted) {
-                if (mTriggerContentUris != null) {
-                    throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
-                            "persisted job");
-                }
-                if (!mTransientExtras.isEmpty()) {
-                    throw new IllegalArgumentException("Can't call setTransientExtras() on a " +
-                            "persisted job");
-                }
-                if (mClipData != null) {
-                    throw new IllegalArgumentException("Can't call setClipData() on a " +
-                            "persisted job");
-                }
-            }
-            if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
-                throw new IllegalArgumentException("An important while foreground job cannot "
-                        + "have a time delay");
-            }
+            // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
+            // check that would ideally be phased out instead.
             if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
                 throw new IllegalArgumentException("An idle mode job will not respect any" +
                         " back-off policy, so calling setBackoffCriteria with" +
                         " setRequiresDeviceIdle is an error.");
             }
-            return new JobInfo(this);
+            JobInfo jobInfo = new JobInfo(this);
+            jobInfo.enforceValidity();
+            return jobInfo;
         }
 
         /**
@@ -1570,6 +1530,59 @@
     }
 
     /**
+     * @hide
+     */
+    public void enforceValidity() {
+        // Check that network estimates require network type
+        if ((networkDownloadBytes > 0 || networkUploadBytes > 0) && networkRequest == null) {
+            throw new IllegalArgumentException(
+                    "Can't provide estimated network usage without requiring a network");
+        }
+
+        // Check that a deadline was not set on a periodic job.
+        if (isPeriodic) {
+            if (maxExecutionDelayMillis != 0L) {
+                throw new IllegalArgumentException(
+                        "Can't call setOverrideDeadline() on a periodic job.");
+            }
+            if (minLatencyMillis != 0L) {
+                throw new IllegalArgumentException(
+                        "Can't call setMinimumLatency() on a periodic job");
+            }
+            if (triggerContentUris != null) {
+                throw new IllegalArgumentException(
+                        "Can't call addTriggerContentUri() on a periodic job");
+            }
+        }
+
+        if (isPersisted) {
+            // We can't serialize network specifiers
+            if (networkRequest != null
+                    && networkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+                throw new IllegalArgumentException(
+                        "Network specifiers aren't supported for persistent jobs");
+            }
+            if (triggerContentUris != null) {
+                throw new IllegalArgumentException(
+                        "Can't call addTriggerContentUri() on a persisted job");
+            }
+            if (!transientExtras.isEmpty()) {
+                throw new IllegalArgumentException(
+                        "Can't call setTransientExtras() on a persisted job");
+            }
+            if (clipData != null) {
+                throw new IllegalArgumentException(
+                        "Can't call setClipData() on a persisted job");
+            }
+        }
+
+        if ((flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && hasEarlyConstraint) {
+            throw new IllegalArgumentException(
+                    "An important while foreground job cannot have a time delay");
+        }
+    }
+
+    /**
      * Convert a priority integer into a human readable string for debugging.
      * @hide
      */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index ab94da8..3d43d20 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -17,7 +17,6 @@
 package android.app.job;
 
 import android.app.Service;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
@@ -26,8 +25,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.lang.ref.WeakReference;
 
 /**
@@ -206,8 +203,7 @@
 
     /**
      * Call in to engine to report that a job has finished executing.  See
-     * {@link JobService#jobFinished(JobParameters, boolean)}  JobService.jobFinished} for more
-     * information.
+     * {@link JobService#jobFinished(JobParameters, boolean)} for more information.
      */
     public void jobFinished(JobParameters params, boolean needsReschedule) {
         if (params == null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 9835e18..9989252 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -884,13 +884,12 @@
         private static final String KEY_MAX_IDLE_TIMEOUT = "max_idle_to";
         private static final String KEY_IDLE_FACTOR = "idle_factor";
         private static final String KEY_MIN_TIME_TO_ALARM = "min_time_to_alarm";
-        // TODO(166121524): update flag names
-        private static final String KEY_MAX_TEMP_APP_WHITELIST_DURATION =
-                "max_temp_app_whitelist_duration";
-        private static final String KEY_MMS_TEMP_APP_WHITELIST_DURATION =
-                "mms_temp_app_whitelist_duration";
-        private static final String KEY_SMS_TEMP_APP_WHITELIST_DURATION =
-                "sms_temp_app_whitelist_duration";
+        private static final String KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS =
+                "max_temp_app_allowlist_duration_ms";
+        private static final String KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS =
+                "mms_temp_app_allowlist_duration_ms";
+        private static final String KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS =
+                "sms_temp_app_allowlist_duration_ms";
         private static final String KEY_NOTIFICATION_ALLOWLIST_DURATION_MS =
                 "notification_allowlist_duration_ms";
         /**
@@ -949,9 +948,9 @@
         private static final float DEFAULT_IDLE_FACTOR = 2f;
         private static final long DEFAULT_MIN_TIME_TO_ALARM =
                 !COMPRESS_TIME ? 30 * 60 * 1000L : 6 * 60 * 1000L;
-        private static final long DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION = 5 * 60 * 1000L;
-        private static final long DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION = 60 * 1000L;
-        private static final long DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION = 20 * 1000L;
+        private static final long DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS = 5 * 60 * 1000L;
+        private static final long DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS = 60 * 1000L;
+        private static final long DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS = 20 * 1000L;
         private static final long DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS = 30 * 1000L;
         private static final boolean DEFAULT_WAIT_FOR_UNLOCK = true;
         private static final float DEFAULT_PRE_IDLE_FACTOR_LONG = 1.67f;
@@ -1142,21 +1141,21 @@
          * Max amount of time to temporarily whitelist an app when it receives a high priority
          * tickle.
          *
-         * @see #KEY_MAX_TEMP_APP_WHITELIST_DURATION
+         * @see #KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS
          */
-        public long MAX_TEMP_APP_WHITELIST_DURATION = DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION;
+        public long MAX_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS;
 
         /**
          * Amount of time we would like to whitelist an app that is receiving an MMS.
-         * @see #KEY_MMS_TEMP_APP_WHITELIST_DURATION
+         * @see #KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS
          */
-        public long MMS_TEMP_APP_WHITELIST_DURATION = DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION;
+        public long MMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS;
 
         /**
          * Amount of time we would like to whitelist an app that is receiving an SMS.
-         * @see #KEY_SMS_TEMP_APP_WHITELIST_DURATION
+         * @see #KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS
          */
-        public long SMS_TEMP_APP_WHITELIST_DURATION = DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION;
+        public long SMS_TEMP_APP_ALLOWLIST_DURATION_MS = DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS;
 
         /**
          * Amount of time we would like to whitelist an app that is handling a
@@ -1303,20 +1302,20 @@
                             MIN_TIME_TO_ALARM = properties.getLong(
                                     KEY_MIN_TIME_TO_ALARM, DEFAULT_MIN_TIME_TO_ALARM);
                             break;
-                        case KEY_MAX_TEMP_APP_WHITELIST_DURATION:
-                            MAX_TEMP_APP_WHITELIST_DURATION = properties.getLong(
-                                    KEY_MAX_TEMP_APP_WHITELIST_DURATION,
-                                    DEFAULT_MAX_TEMP_APP_WHITELIST_DURATION);
+                        case KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS:
+                            MAX_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+                                    KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
+                                    DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS);
                             break;
-                        case KEY_MMS_TEMP_APP_WHITELIST_DURATION:
-                            MMS_TEMP_APP_WHITELIST_DURATION = properties.getLong(
-                                    KEY_MMS_TEMP_APP_WHITELIST_DURATION,
-                                    DEFAULT_MMS_TEMP_APP_WHITELIST_DURATION);
+                        case KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS:
+                            MMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+                                    KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                                    DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS);
                             break;
-                        case KEY_SMS_TEMP_APP_WHITELIST_DURATION:
-                            SMS_TEMP_APP_WHITELIST_DURATION = properties.getLong(
-                                    KEY_SMS_TEMP_APP_WHITELIST_DURATION,
-                                    DEFAULT_SMS_TEMP_APP_WHITELIST_DURATION);
+                        case KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS:
+                            SMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
+                                    KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                                    DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS);
                             break;
                         case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS:
                             NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong(
@@ -1438,16 +1437,16 @@
             TimeUtils.formatDuration(MIN_TIME_TO_ALARM, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MAX_TEMP_APP_WHITELIST_DURATION); pw.print("=");
-            TimeUtils.formatDuration(MAX_TEMP_APP_WHITELIST_DURATION, pw);
+            pw.print("    "); pw.print(KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+            TimeUtils.formatDuration(MAX_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MMS_TEMP_APP_WHITELIST_DURATION); pw.print("=");
-            TimeUtils.formatDuration(MMS_TEMP_APP_WHITELIST_DURATION, pw);
+            pw.print("    "); pw.print(KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+            TimeUtils.formatDuration(MMS_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_SMS_TEMP_APP_WHITELIST_DURATION); pw.print("=");
-            TimeUtils.formatDuration(SMS_TEMP_APP_WHITELIST_DURATION, pw);
+            pw.print("    "); pw.print(KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS); pw.print("=");
+            TimeUtils.formatDuration(SMS_TEMP_APP_ALLOWLIST_DURATION_MS, pw);
             pw.println();
 
             pw.print("    "); pw.print(KEY_NOTIFICATION_ALLOWLIST_DURATION_MS); pw.print("=");
@@ -1804,29 +1803,29 @@
         public long whitelistAppTemporarily(String packageName, int userId, String reason)
                 throws RemoteException {
             // At least 10 seconds.
-            long duration = Math.max(10_000L, mConstants.MAX_TEMP_APP_WHITELIST_DURATION / 2);
-            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
-            return duration;
+            long durationMs = Math.max(10_000L, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS / 2);
+            addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+            return durationMs;
         }
 
         @Override
         public void addPowerSaveTempWhitelistApp(String packageName, long duration,
                 int userId, String reason) throws RemoteException {
-            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
+            addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reason);
         }
 
         @Override public long addPowerSaveTempWhitelistAppForMms(String packageName,
                 int userId, String reason) throws RemoteException {
-            long duration = mConstants.MMS_TEMP_APP_WHITELIST_DURATION;
-            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
-            return duration;
+            long durationMs = mConstants.MMS_TEMP_APP_ALLOWLIST_DURATION_MS;
+            addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+            return durationMs;
         }
 
         @Override public long addPowerSaveTempWhitelistAppForSms(String packageName,
                 int userId, String reason) throws RemoteException {
-            long duration = mConstants.SMS_TEMP_APP_WHITELIST_DURATION;
-            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
-            return duration;
+            long durationMs = mConstants.SMS_TEMP_APP_ALLOWLIST_DURATION_MS;
+            addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason);
+            return durationMs;
         }
 
         @Override public void exitIdle(String reason) {
@@ -1900,7 +1899,7 @@
         @Override
         public void addPowerSaveTempWhitelistApp(int callingUid, String packageName,
                 long duration, int userId, boolean sync, String reason) {
-            addPowerSaveTempWhitelistAppInternal(callingUid, packageName, duration,
+            addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration,
                     userId, sync, reason);
         }
 
@@ -2617,7 +2616,7 @@
         }
     }
 
-    void addPowerSaveTempWhitelistAppChecked(String packageName, long duration,
+    void addPowerSaveTempAllowlistAppChecked(String packageName, long duration,
             int userId, String reason) throws RemoteException {
         getContext().enforceCallingPermission(
                 Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
@@ -2632,14 +2631,14 @@
                 "addPowerSaveTempWhitelistApp", null);
         final long token = Binder.clearCallingIdentity();
         try {
-            addPowerSaveTempWhitelistAppInternal(callingUid,
+            addPowerSaveTempAllowlistAppInternal(callingUid,
                     packageName, duration, userId, true, reason);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    void removePowerSaveTempWhitelistAppChecked(String packageName, int userId)
+    void removePowerSaveTempAllowlistAppChecked(String packageName, int userId)
             throws RemoteException {
         getContext().enforceCallingPermission(
                 Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
@@ -2654,7 +2653,7 @@
                 "removePowerSaveTempWhitelistApp", null);
         final long token = Binder.clearCallingIdentity();
         try {
-            removePowerSaveTempWhitelistAppInternal(packageName, userId);
+            removePowerSaveTempAllowlistAppInternal(packageName, userId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2664,7 +2663,7 @@
      * Adds an app to the temporary whitelist and resets the endTime for granting the
      * app an exemption to access network and acquire wakelocks.
      */
-    void addPowerSaveTempWhitelistAppInternal(int callingUid, String packageName,
+    void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName,
             long duration, int userId, boolean sync, String reason) {
         try {
             int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId);
@@ -2690,7 +2689,7 @@
                             + " is not on whitelist");
                 }
             }
-            duration = Math.min(duration, mConstants.MAX_TEMP_APP_WHITELIST_DURATION);
+            duration = Math.min(duration, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS);
             Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.get(appId);
             final boolean newEntry = entry == null;
             // Set the new end time
@@ -2728,7 +2727,7 @@
     /**
      * Removes an app from the temporary whitelist and notifies the observers.
      */
-    private void removePowerSaveTempWhitelistAppInternal(String packageName, int userId) {
+    private void removePowerSaveTempAllowlistAppInternal(String packageName, int userId) {
         try {
             final int uid = getContext().getPackageManager().getPackageUidAsUser(
                     packageName, userId);
@@ -4354,9 +4353,9 @@
             if (arg != null) {
                 try {
                     if (removePkg) {
-                        removePowerSaveTempWhitelistAppChecked(arg, shell.userId);
+                        removePowerSaveTempAllowlistAppChecked(arg, shell.userId);
                     } else {
-                        addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
+                        addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, "shell");
                     }
                 } catch (Exception e) {
                     pw.println("Failed: " + e);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index fe96952..255e2f6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1054,6 +1054,7 @@
 
     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
             int userId, String tag) {
+        // Rate limit excessive schedule() calls.
         final String servicePkg = job.getService().getPackageName();
         if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
             // Only limit schedule calls for persisted jobs scheduled by the app itself.
@@ -1358,8 +1359,7 @@
                 for (int i=0; i<mActiveServices.size(); i++) {
                     JobServiceContext jsc = mActiveServices.get(i);
                     final JobStatus executing = jsc.getRunningJobLocked();
-                    if (executing != null
-                            && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
+                    if (executing != null && !executing.canRunInDoze()) {
                         jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
                                 "cancelled due to doze");
                     }
@@ -1411,7 +1411,7 @@
                 final JobServiceContext jsc = mActiveServices.get(i);
                 final JobStatus job = jsc.getRunningJobLocked();
                 if (job != null
-                        && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
+                        && !job.canRunInDoze()
                         && !job.dozeWhitelisted
                         && !job.uidActive) {
                     // We will report active if we have a job running and it is not an exception
@@ -2600,6 +2600,7 @@
         // job that runs one of the app's services, as well as verifying that the
         // named service properly requires the BIND_JOB_SERVICE permission
         private void enforceValidJobRequest(int uid, JobInfo job) {
+            job.enforceValidity();
             final PackageManager pm = getContext()
                     .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
                     .getPackageManager();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1e72062..cc20213 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -20,10 +20,11 @@
 import android.app.AppGlobals;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.os.BasicShellCommandHandler;
 import android.os.Binder;
 import android.os.UserHandle;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintWriter;
 
 public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 44c8fcf..f647db9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -189,8 +189,7 @@
         final String packageName = jobStatus.getSourcePackageName();
 
         final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
-                (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
-                        != 0);
+                jobStatus.canRunInBatterySaver());
 
         final boolean isActive;
         if (activeState == UNKNOWN) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 67997cf..6ddafad 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -464,7 +464,7 @@
             NetworkCapabilities capabilities) {
         // TODO: consider matching against non-active networks
 
-        final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+        final boolean ignoreBlocked = jobStatus.shouldIgnoreNetworkBlocking();
         final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
                 jobStatus.getSourceUid(), ignoreBlocked);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 00dbb82..8f6c68d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -470,7 +470,7 @@
         }
         this.requiredConstraints = requiredConstraints;
         mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
-        mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+        mReadyNotDozing = canRunInDoze();
         if (standbyBucket == RESTRICTED_INDEX) {
             addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
         } else {
@@ -1036,6 +1036,22 @@
         mPersistedUtcTimes = null;
     }
 
+    /**
+     * @return true if the job is exempted from Doze restrictions and therefore allowed to run
+     * in Doze.
+     */
+    public boolean canRunInDoze() {
+        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+    }
+
+    boolean canRunInBatterySaver() {
+        return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0;
+    }
+
+    boolean shouldIgnoreNetworkBlocking() {
+        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+    }
+
     /** @return true if the constraint was changed, false otherwise. */
     boolean setChargingConstraintSatisfied(boolean state) {
         return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
@@ -1086,7 +1102,7 @@
         dozeWhitelisted = whitelisted;
         if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
             // The constraint was changed. Update the ready flag.
-            mReadyNotDozing = state || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+            mReadyNotDozing = state || canRunInDoze();
             return true;
         }
         return false;
@@ -1771,9 +1787,11 @@
             pw.print(prefix); pw.print("  readyDeadlineSatisfied: ");
             pw.println(mReadyDeadlineSatisfied);
         }
-        pw.print(prefix);
-        pw.print("  readyDynamicSatisfied: ");
-        pw.println(mReadyDynamicSatisfied);
+        if (mDynamicConstraints != 0) {
+            pw.print(prefix);
+            pw.print("  readyDynamicSatisfied: ");
+            pw.println(mReadyDynamicSatisfied);
+        }
         pw.print(prefix);
         pw.print("  readyComponentEnabled: ");
         pw.println(serviceInfo != null);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 7d7de3b..1d72b42 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -1679,42 +1679,47 @@
             // Update job bookkeeping out of band.
             JobSchedulerBackgroundThread.getHandler().post(() -> {
                 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
-                if (DEBUG) {
-                    Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
-                            + bucketIndex);
-                }
-                List<JobStatus> restrictedChanges = new ArrayList<>();
-                synchronized (mLock) {
-                    ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
-                    if (jobs == null || jobs.size() == 0) {
-                        return;
-                    }
-                    for (int i = jobs.size() - 1; i >= 0; i--) {
-                        JobStatus js = jobs.valueAt(i);
-                        // Effective standby bucket can change after this in some situations so
-                        // use the real bucket so that the job is tracked by the controllers.
-                        if ((bucketIndex == RESTRICTED_INDEX
-                                || js.getStandbyBucket() == RESTRICTED_INDEX)
-                                && bucketIndex != js.getStandbyBucket()) {
-                            restrictedChanges.add(js);
-                        }
-                        js.setStandbyBucket(bucketIndex);
-                    }
-                    Timer timer = mPkgTimers.get(userId, packageName);
-                    if (timer != null && timer.isActive()) {
-                        timer.rescheduleCutoff();
-                    }
-                    if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
-                        mStateChangedListener.onControllerStateChanged();
-                    }
-                }
-                if (restrictedChanges.size() > 0) {
-                    mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
-                }
+                updateStandbyBucket(userId, packageName, bucketIndex);
             });
         }
     }
 
+    @VisibleForTesting
+    void updateStandbyBucket(
+            final int userId, final @NonNull String packageName, final int bucketIndex) {
+        if (DEBUG) {
+            Slog.i(TAG, "Moving pkg " + string(userId, packageName)
+                    + " to bucketIndex " + bucketIndex);
+        }
+        List<JobStatus> restrictedChanges = new ArrayList<>();
+        synchronized (mLock) {
+            ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+            if (jobs == null || jobs.size() == 0) {
+                return;
+            }
+            for (int i = jobs.size() - 1; i >= 0; i--) {
+                JobStatus js = jobs.valueAt(i);
+                // Effective standby bucket can change after this in some situations so
+                // use the real bucket so that the job is tracked by the controllers.
+                if ((bucketIndex == RESTRICTED_INDEX || js.getStandbyBucket() == RESTRICTED_INDEX)
+                        && bucketIndex != js.getStandbyBucket()) {
+                    restrictedChanges.add(js);
+                }
+                js.setStandbyBucket(bucketIndex);
+            }
+            Timer timer = mPkgTimers.get(userId, packageName);
+            if (timer != null && timer.isActive()) {
+                timer.rescheduleCutoff();
+            }
+            if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
+                mStateChangedListener.onControllerStateChanged();
+            }
+        }
+        if (restrictedChanges.size() > 0) {
+            mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
+        }
+    }
+
     private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
         private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
             public boolean test(TimingSession ts) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0f60eae..8363e9f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -42,6 +42,7 @@
 import "frameworks/base/core/proto/android/server/location/enums.proto";
 import "frameworks/base/core/proto/android/service/procstats_enum.proto";
 import "frameworks/base/core/proto/android/service/usb.proto";
+import "frameworks/base/core/proto/android/stats/camera/camera.proto";
 import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto";
 import "frameworks/base/core/proto/android/stats/connectivity/tethering.proto";
 import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto";
@@ -60,6 +61,7 @@
 import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
 import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
 import "frameworks/base/core/proto/android/stats/tls/enums.proto";
+import "frameworks/base/core/proto/android/stats/tv/tif_enums.proto";
 import "frameworks/base/core/proto/android/telecomm/enums.proto";
 import "frameworks/base/core/proto/android/telephony/enums.proto";
 import "frameworks/base/core/proto/android/view/enums.proto";
@@ -510,6 +512,8 @@
         MediaPlaybackTrackChanged media_playback_track_changed = 324;
         WifiScanReported wifi_scan_reported = 325 [(module) = "wifi"];
         WifiPnoScanReported wifi_pno_scan_reported = 326  [(module) = "wifi"];
+        TifTuneStateChanged tif_tune_changed = 327 [(module) = "framework"];
+        AutoRotateReported auto_rotate_reported = 328 [(module) = "framework"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -3766,6 +3770,7 @@
         WARM = 1;
         HOT = 2;
         COLD = 3;
+        RELAUNCH = 4;
     }
     // The transition type.
     optional TransitionType type = 3;
@@ -3827,6 +3832,7 @@
         WARM = 1;
         HOT = 2;
         COLD = 3;
+        RELAUNCH = 4;
     }
     // The transition type.
     optional TransitionType type = 3;
@@ -9946,10 +9952,12 @@
  *   frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java
  */
 message CameraActionEvent {
-    // Camera session duration
+    // Camera session duration in milliseconds if action is SESSION.
+    // 0 if action is OPEN or CLOSE.
     optional int64 duration_millis = 1;
 
-    // Camera API level used
+    // Camera API level used.
+    // 1 for camera1 API, and 2 for camera2 API.
     optional int32 api_level = 2;
 
     // Name of client package
@@ -9963,6 +9971,56 @@
         EXTERNAL = 3;
     }
     optional Facing facing = 4;
+
+    // Camera ID
+    optional string camera_id = 5;
+
+    // Camera action type
+    enum Action {
+        UNKNOWN_ACTION = 0;
+        OPEN = 1;
+        CLOSE = 2;
+        SESSION = 3;
+    }
+    optional Action action = 6;
+
+    // Whether the client is accessing camera using ndk
+    optional bool is_ndk = 7;
+
+    // Action OPEN: Open latency
+    // Action CLOSE: Close latency
+    // Action SESSION: Camera session creation duration.
+    //                 If this entry is reusing an existing session, the value is -1.
+    optional int32 latency_millis = 8;
+
+    // session type: 0 for normal mode, 1 for constrained high speed mode
+    optional int32 operating_mode = 9;
+
+    // If actioh is SESSION: number of internal reconfigurations
+    // Else: 0
+    optional int32 internal_reconfig = 10;
+
+    // Number of requests for this capture session. Only applicable to SESSION
+    // action.
+    optional int64 request_count = 11;
+    // Number of result errors. Only applicable to SESSION action.
+    optional int64 result_error_count = 12;
+    // Whether the device runs into error state.
+    optional bool device_error = 13;
+
+    // If action is SESSION: Stream states
+    // Else: stream_count = 0
+    optional int32 stream_count = 14;
+    optional android.stats.camera.CameraStreamProto stream_1 = 15
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+    optional android.stats.camera.CameraStreamProto stream_2 = 16
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+    optional android.stats.camera.CameraStreamProto stream_3 = 17
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+    optional android.stats.camera.CameraStreamProto stream_4 = 18
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+    optional android.stats.camera.CameraStreamProto stream_5 = 19
+    [(android.os.statsd.log_mode) = MODE_BYTES];
 }
 
 /**
@@ -10352,6 +10410,39 @@
 }
 
 /**
+ * Logs when a TV Input Service Session changes tune state
+ * This is atom ID 327.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
+ */
+message TifTuneStateChanged {
+
+    // Tv Input Service uid, TV Player uid
+    repeated AttributionNode attribution_node = 1 [
+        (state_field_option).primary_field_first_uid = true
+    ];
+    optional android.stats.tv.TifTuneState state = 2 [
+        (state_field_option).exclusive_state = true,
+        (state_field_option).default_state_value = 0,
+        (state_field_option).nested = false
+    ];
+    // This a globally unique 128 bit random number created by TvInputManagerService when
+    // android.media.tv.TvInputManager#createSession is called.
+    // It is has no device or user association.
+    // See android.media.tv.TvInputService.onCreateSession(java.lang.String, java.lang.String)
+    // WARNING: Any changes to this field should be carefully reviewed for privacy.
+    //          Inspect the code at
+    //          framework/base/cmds/statsd/src/atoms.proto
+    //               TifTuneState
+    //          frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
+    //              logTuneStateChanged
+    //              BinderService.createSession
+    //              SessionState.sessionId
+    optional string tif_session_id = 3 [(state_field_option).primary_field = true];
+}
+
+/**
  * Logs when a tune occurs through device's Frontend.
  * This is atom ID 276.
  *
@@ -12077,6 +12168,33 @@
 }
 
 /**
+ * Logs when an auto rotate event occurs while smart auto rotate is enabled.
+ */
+message AutoRotateReported {
+    enum Orientation {
+        UNKNOWN = 1;
+        ROTATION_0 = 2;
+        ROTATION_90 = 3;
+        ROTATION_180 = 4;
+        ROTATION_270 = 5;
+        DISABLED = 6;
+        UNAVAILABLE = 7;
+        FAILURE = 8;
+    }
+
+    // Orientation of the device when a rotation was detected.
+    optional Orientation current_orientation = 1;
+    // The orientation of the phone after rotation before going through the recommendation service.
+    optional Orientation proposed_orientation = 2;
+    // Orientation recommended by the smart autorotate service component outside of the platform. It
+    // may or may not match the proposed_orientation. Can be disabled or unavailable if the
+    // recommendation service is disabled or unavailable. Will be unknown if the service failed.
+    optional Orientation recommended_orientation = 3;
+    // Time taken to calculate the rotation recommendation.
+    optional int64 recommendation_process_duration_millis = 4;
+}
+
+/**
   * Pushes TLS handshake counters from Conscrypt.
   * Pulled from:
   *   external/conscrypt/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 822b40b..8d2f143 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -5575,7 +5575,6 @@
 android.os.BadParcelableException
 android.os.BaseBundle$NoImagePreloadHolder
 android.os.BaseBundle
-android.os.BasicShellCommandHandler
 android.os.BatteryManager
 android.os.BatteryManagerInternal
 android.os.BatteryProperty$1
@@ -6750,6 +6749,7 @@
 android.sysprop.-$$Lambda$TelephonyProperties$klELuV5zVSqFveC5l6c3FSJmLAU
 android.sysprop.-$$Lambda$TelephonyProperties$pFU8zg4eHAdooeRLJg1WBG52cKk
 android.sysprop.-$$Lambda$TelephonyProperties$sXc3eBCFirzHWb9pvClH7EsiM_Q
+android.stats.camera.nano.CameraStreamProto
 android.sysprop.AdbProperties
 android.sysprop.ApexProperties
 android.sysprop.ContactsProperties
@@ -11756,6 +11756,7 @@
 com.android.net.module.annotation.VisibleForTesting
 com.android.net.module.annotation.WorkerThread
 com.android.net.module.util.InetAddressUtils
+com.android.modules.utils.BasicShellCommandHandler
 com.android.okhttp.Address
 com.android.okhttp.AndroidShimResponseCache
 com.android.okhttp.Authenticator
diff --git a/core/api/current.txt b/core/api/current.txt
index f665512..e15c559 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10158,6 +10158,7 @@
     method public abstract void grantUriPermission(String, android.net.Uri, int);
     method public abstract boolean isDeviceProtectedStorage();
     method public boolean isRestricted();
+    method public static boolean isUiContext(@NonNull android.content.Context);
     method public abstract boolean moveDatabaseFrom(android.content.Context, String);
     method public abstract boolean moveSharedPreferencesFrom(android.content.Context, String);
     method @NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[]);
@@ -12332,7 +12333,7 @@
     field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
     field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
     field public static final int SYNCHRONOUS = 2; // 0x2
-    field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
+    field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
@@ -12344,6 +12345,23 @@
     ctor public PackageManager.NameNotFoundException(String);
   }
 
+  public static final class PackageManager.Property implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean getBoolean();
+    method public float getFloat();
+    method public int getInteger();
+    method @NonNull public String getName();
+    method public int getResourceId();
+    method @Nullable public String getString();
+    method public boolean isBoolean();
+    method public boolean isFloat();
+    method public boolean isInteger();
+    method public boolean isResourceId();
+    method public boolean isString();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.Property> CREATOR;
+  }
+
   @Deprecated public class PackageStats implements android.os.Parcelable {
     ctor @Deprecated public PackageStats(String);
     ctor @Deprecated public PackageStats(android.os.Parcel);
@@ -35793,6 +35811,7 @@
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectCredentialProtectedWhileLocked();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectFileUriExposure();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectImplicitDirectBoot();
+    method @NonNull public android.os.StrictMode.VmPolicy.Builder detectIncorrectContextUse();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects();
@@ -46190,12 +46209,6 @@
     field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1
   }
 
-  public final class PhoneCapability implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR;
-  }
-
   public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
     ctor public PhoneNumberFormattingTextWatcher();
     ctor public PhoneNumberFormattingTextWatcher(String);
@@ -46264,10 +46277,10 @@
 
   public class PhoneStateListener {
     ctor public PhoneStateListener();
-    ctor @Deprecated public PhoneStateListener(@NonNull java.util.concurrent.Executor);
+    ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
     method public void onActiveDataSubscriptionIdChanged(int);
     method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int);
+    method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
     method public void onCallForwardingIndicatorChanged(boolean);
     method public void onCallStateChanged(int, String);
     method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
@@ -46275,144 +46288,36 @@
     method public void onDataActivity(int);
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
+    method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
     method public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
+    method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
     method public void onMessageWaitingIndicatorChanged(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
+    method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
     method public void onServiceStateChanged(android.telephony.ServiceState);
     method @Deprecated public void onSignalStrengthChanged(int);
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
     method public void onUserMobileDataStateChanged(boolean);
-    field @Deprecated public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
-    field @Deprecated public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
-    field @Deprecated public static final int LISTEN_CALL_STATE = 32; // 0x20
-    field @Deprecated public static final int LISTEN_CELL_INFO = 1024; // 0x400
-    field @Deprecated public static final int LISTEN_CELL_LOCATION = 16; // 0x10
-    field @Deprecated public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
-    field @Deprecated public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
-    field @Deprecated public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
-    field @Deprecated public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
-    field @Deprecated public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
+    field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
+    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
+    field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
+    field public static final int LISTEN_CALL_STATE = 32; // 0x20
+    field public static final int LISTEN_CELL_INFO = 1024; // 0x400
+    field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
+    field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
+    field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+    field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
+    field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
+    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
+    field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
     field public static final int LISTEN_NONE = 0; // 0x0
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
-    field @Deprecated public static final int LISTEN_SERVICE_STATE = 1; // 0x1
+    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
+    field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
     field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
-    field @Deprecated public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
-    field @Deprecated public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
-  }
-
-  public static interface PhoneStateListener.ActiveDataSubscriptionIdChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onActiveDataSubscriptionIdChanged(int);
-  }
-
-  public static interface PhoneStateListener.AlwaysReportedSignalStrengthsChangedListener {
-    method @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength);
-  }
-
-  public static interface PhoneStateListener.BarringInfoChangedListener {
-    method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
-  }
-
-  public static interface PhoneStateListener.CallDisconnectCauseChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int);
-  }
-
-  public static interface PhoneStateListener.CallForwardingIndicatorChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onCallForwardingIndicatorChanged(boolean);
-  }
-
-  public static interface PhoneStateListener.CallStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public void onCallStateChanged(int, @Nullable String);
-  }
-
-  public static interface PhoneStateListener.CarrierNetworkChangeListener {
-    method public void onCarrierNetworkChange(boolean);
-  }
-
-  public static interface PhoneStateListener.CellInfoChangedListener {
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellInfoChanged(@NonNull java.util.List<android.telephony.CellInfo>);
-  }
-
-  public static interface PhoneStateListener.CellLocationChangedListener {
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void onCellLocationChanged(@NonNull android.telephony.CellLocation);
-  }
-
-  public static interface PhoneStateListener.DataActivationStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivationStateChanged(int);
-  }
-
-  public static interface PhoneStateListener.DataActivityListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataActivity(int);
-  }
-
-  public static interface PhoneStateListener.DataConnectionStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onDataConnectionStateChanged(int, int);
-  }
-
-  public static interface PhoneStateListener.DisplayInfoChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
-  }
-
-  public static interface PhoneStateListener.EmergencyNumberListChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
-  }
-
-  public static interface PhoneStateListener.ImsCallDisconnectCauseChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
-  }
-
-  public static interface PhoneStateListener.MessageWaitingIndicatorChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean);
-  }
-
-  public static interface PhoneStateListener.PhoneCapabilityChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
-  }
-
-  public static interface PhoneStateListener.PhysicalChannelConfigChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
-  }
-
-  public static interface PhoneStateListener.PreciseDataConnectionStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
-  }
-
-  public static interface PhoneStateListener.RegistrationFailedListener {
-    method @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
-  }
-
-  public static interface PhoneStateListener.ServiceStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onServiceStateChanged(@NonNull android.telephony.ServiceState);
-  }
-
-  public static interface PhoneStateListener.SignalStrengthsChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onSignalStrengthsChanged(@NonNull android.telephony.SignalStrength);
-  }
-
-  public static interface PhoneStateListener.UserMobileDataStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onUserMobileDataStateChanged(boolean);
-  }
-
-  public final class PhysicalChannelConfig implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getCellBandwidthDownlink();
-    method public int getChannelNumber();
-    method public int getConnectionStatus();
-    method public int getNetworkType();
-    method @IntRange(from=0, to=1007) public int getPhysicalCellId();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int CHANNEL_NUMBER_UNKNOWN = -1; // 0xffffffff
-    field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
-    field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
-    field public static final int CONNECTION_UNKNOWN = -1; // 0xffffffff
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhysicalChannelConfig> CREATOR;
-    field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
+    field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
+    field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
   }
 
   public final class PreciseDataConnectionState implements android.os.Parcelable {
@@ -46595,6 +46500,8 @@
     field public static final int RESULT_RECEIVE_WHILE_ENCRYPTED = 504; // 0x1f8
     field public static final int RESULT_REMOTE_EXCEPTION = 31; // 0x1f
     field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+    field public static final int RESULT_RIL_ACCESS_BARRED = 122; // 0x7a
+    field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b
     field public static final int RESULT_RIL_CANCELLED = 119; // 0x77
     field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d
     field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71
@@ -46613,6 +46520,7 @@
     field public static final int RESULT_RIL_RADIO_NOT_AVAILABLE = 100; // 0x64
     field public static final int RESULT_RIL_REQUEST_NOT_SUPPORTED = 114; // 0x72
     field public static final int RESULT_RIL_REQUEST_RATE_LIMITED = 106; // 0x6a
+    field public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; // 0x79
     field public static final int RESULT_RIL_SIM_ABSENT = 120; // 0x78
     field public static final int RESULT_RIL_SMS_SEND_FAIL_RETRY = 101; // 0x65
     field public static final int RESULT_RIL_SYSTEM_ERR = 108; // 0x6c
@@ -46921,8 +46829,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
-    method @Deprecated public void listen(long, @NonNull android.telephony.PhoneStateListener);
-    method public void registerPhoneStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.PhoneStateListener);
+    method public void listen(long, @NonNull android.telephony.PhoneStateListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(String);
@@ -46944,7 +46851,6 @@
     method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri);
     method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int);
-    method public void unregisterPhoneStateListener(@NonNull android.telephony.PhoneStateListener);
     method public void updateAvailableNetworks(@NonNull java.util.List<android.telephony.AvailableNetworkInfo>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     field public static final String ACTION_CARRIER_MESSAGING_CLIENT_SERVICE = "android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE";
     field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
@@ -52924,6 +52830,7 @@
     method public boolean performHapticFeedback(int, int);
     method public boolean performLongClick();
     method public boolean performLongClick(float, float);
+    method @Nullable public android.view.OnReceiveContentListener.Payload performReceiveContent(@NonNull android.view.OnReceiveContentListener.Payload);
     method public void playSoundEffect(int);
     method public boolean post(Runnable);
     method public boolean postDelayed(Runnable, long);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6cf93fc..2ebbbf5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2066,6 +2066,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
   }
 
+  public static class LauncherApps.ShortcutQuery {
+    field public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
+  }
+
   public class PackageInstaller {
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
@@ -5027,14 +5031,15 @@
     method public void clearResourceLostListener();
     method public void close();
     method public int connectCiCam(int);
+    method public int connectFrontendToCiCam(int);
     method public int disconnectCiCam();
+    method public int disconnectFrontendToCiCam(int);
     method public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter);
     method public long getAvSyncTime(int);
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
-    method public int linkFrontendToCiCam(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
     method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -5048,7 +5053,6 @@
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
-    method public int unlinkFrontendToCiCam(int);
     method public void updateResourcePriority(int, int);
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
@@ -5249,7 +5253,7 @@
   public class Filter implements java.lang.AutoCloseable {
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
-    method public int configureScramblingStatusEvent(int);
+    method public int configureMonitorEvent(int);
     method public int flush();
     method public int getId();
     method public long getId64Bit();
@@ -5257,6 +5261,8 @@
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int start();
     method public int stop();
+    field public static final int MONITOR_EVENT_IP_CID_CHANGE = 2; // 0x2
+    field public static final int MONITOR_EVENT_SCRAMBLING_STATUS = 1; // 0x1
     field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
     field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
     field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
@@ -5303,6 +5309,10 @@
     ctor public FilterEvent();
   }
 
+  public final class IpCidChangeEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getIpCid();
+  }
+
   public final class IpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
     method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
     method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
@@ -8953,11 +8963,8 @@
   public abstract class ImpressionAttestationService extends android.app.Service {
     ctor public ImpressionAttestationService();
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
-    method public abstract int onVerifyImpressionToken(@NonNull android.service.attestation.ImpressionToken);
-    field public static final int VERIFICATION_STATUS_APP_DECLARED = 2; // 0x2
-    field public static final int VERIFICATION_STATUS_OS_VERIFIED = 1; // 0x1
-    field public static final int VERIFICATION_STATUS_UNKNOWN = 0; // 0x0
+    method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull String, @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
+    method public abstract boolean onVerifyImpressionToken(@NonNull String, @NonNull android.service.attestation.ImpressionToken);
   }
 
   public final class ImpressionToken implements android.os.Parcelable {
@@ -10340,83 +10347,35 @@
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
+    method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
+    method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
     method public void onVoiceActivationStateChanged(int);
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23; // 0x17
-    field @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10; // 0xa
-    field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_BARRING_INFO_CHANGED = 32; // 0x20
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27; // 0x1b
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26; // 0x1a
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4
-    field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6
-    field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11
-    field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb
-    field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5
-    field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13
-    field public static final int EVENT_DATA_ACTIVITY_CHANGED = 8; // 0x8
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14; // 0xe
-    field public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7; // 0x7
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_DATA_ENABLED_CHANGED = 34; // 0x22
-    field public static final int EVENT_DISPLAY_INFO_CHANGED = 21; // 0x15
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25; // 0x19
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c
-    field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3
-    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf
-    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d
-    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30; // 0x1e
-    field public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22; // 0x16
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33; // 0x21
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12; // 0xc
-    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd
-    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18
-    field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f
-    field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
-    field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
-    field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
-    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
-    field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
-    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
-    field @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
+    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final long LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 4294967296L; // 0x100000000L
+    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
+    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
+    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
+    field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
   }
 
-  public static interface PhoneStateListener.CallAttributesChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
-  }
-
-  public static interface PhoneStateListener.DataEnabledChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int);
-  }
-
-  public static interface PhoneStateListener.OutgoingEmergencyCallListener {
-    method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
-  }
-
-  public static interface PhoneStateListener.OutgoingEmergencySmsListener {
-    method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
-  }
-
-  public static interface PhoneStateListener.PreciseCallStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
-  }
-
-  public static interface PhoneStateListener.RadioPowerStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
-  }
-
-  public static interface PhoneStateListener.SrvccStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
-  }
-
-  public static interface PhoneStateListener.VoiceActivationStateChangedListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onVoiceActivationStateChanged(int);
+  public final class PhysicalChannelConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCellBandwidthDownlink();
+    method public int getChannelNumber();
+    method public int getConnectionStatus();
+    method public int getNetworkType();
+    method @IntRange(from=0, to=1007) public int getPhysicalCellId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CHANNEL_NUMBER_UNKNOWN = -1; // 0xffffffff
+    field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
+    field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
+    field public static final int CONNECTION_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhysicalChannelConfig> CREATOR;
+    field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
   }
 
   public final class PinResult implements android.os.Parcelable {
@@ -10826,6 +10785,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccSlotInfo[] getUiccSlotsInfo();
     method @Nullable public android.os.Bundle getVisualVoicemailSettings();
@@ -11082,6 +11042,35 @@
     field public static final String TYPE_XCAP_STRING = "xcap";
   }
 
+  public final class ApnThrottleStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getApnType();
+    method public int getRetryType();
+    method public int getSlotIndex();
+    method public long getThrottleExpiryTimeMillis();
+    method public int getThrottleType();
+    method public int getTransportType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.ApnThrottleStatus> CREATOR;
+    field public static final int RETRY_TYPE_HANDOVER = 3; // 0x3
+    field public static final int RETRY_TYPE_NEW_CONNECTION = 2; // 0x2
+    field public static final int RETRY_TYPE_NONE = 1; // 0x1
+    field public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; // 0x2
+    field public static final int THROTTLE_TYPE_NONE = 1; // 0x1
+  }
+
+  public static final class ApnThrottleStatus.Builder {
+    ctor public ApnThrottleStatus.Builder();
+    method @NonNull public android.telephony.data.ApnThrottleStatus build();
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setApnType(int);
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setNoThrottle();
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setRetryType(int);
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setSlotIndex(int);
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setThrottleExpiryTimeMillis(long);
+    method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setTransportType(int);
+    field public static final long NO_THROTTLE_EXPIRY_TIME = -1L; // 0xffffffffffffffffL
+  }
+
   public final class DataCallResponse implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.net.LinkAddress> getAddresses();
@@ -11199,6 +11188,7 @@
     method public abstract void close();
     method public void deactivateDataCall(int, int, @Nullable android.telephony.data.DataServiceCallback);
     method public final int getSlotIndex();
+    method public final void notifyApnUnthrottled(@NonNull String);
     method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
     method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
     method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
@@ -11209,6 +11199,7 @@
   }
 
   public class DataServiceCallback {
+    method public void onApnUnthrottled(@NonNull String);
     method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>);
     method public void onDeactivateDataCallComplete(int);
     method public void onHandoverCancelled(int);
@@ -11234,6 +11225,7 @@
     ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int);
     method public abstract void close();
     method public final int getSlotIndex();
+    method public void reportApnThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ApnThrottleStatus>);
     method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f695adf..52a79ba 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -302,8 +302,10 @@
     method public void destroy();
     method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String);
     method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
+    method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
     method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
     method public void syncInputTransactions();
+    method public void syncInputTransactions(boolean);
   }
 
   public class UiModeManager {
@@ -1227,7 +1229,6 @@
   }
 
   public static final class StrictMode.VmPolicy.Builder {
-    method @NonNull public android.os.StrictMode.VmPolicy.Builder detectIncorrectContextUse();
     method @NonNull public android.os.StrictMode.VmPolicy.Builder permitIncorrectContextUse();
   }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index b1b9f41..b15fa27 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -650,7 +650,6 @@
                     0);
             flags = asAttributes.getInt(
                     com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
-            flags |= FLAG_REQUEST_2_FINGER_PASSTHROUGH;
             mSettingsActivityName = asAttributes.getString(
                     com.android.internal.R.styleable.AccessibilityService_settingsActivity);
             if (asAttributes.getBoolean(com.android.internal.R.styleable
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d9f34d8..cdfe41e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -34,6 +34,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.DatabaseUtils;
@@ -7617,7 +7618,7 @@
                         // Only collect app-ops when the proxy is trusted
                         && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
                         myUid) == PackageManager.PERMISSION_GRANTED
-                        || isTrustedVoiceServiceProxy(mContext.getOpPackageName(), op))) {
+                        || isTrustedVoiceServiceProxy(mContext, mContext.getOpPackageName(), op))) {
                     collectNotedOpSync(op, proxiedAttributionTag);
                 }
             }
@@ -7628,30 +7629,43 @@
         }
     }
 
-    private boolean isTrustedVoiceServiceProxy(String packageName, int code) {
+    /**
+     * Checks if the voice recognition service is a trust proxy.
+     *
+     * @return {@code true} if the package is a trust voice recognition service proxy
+     * @hide
+     */
+    public static boolean isTrustedVoiceServiceProxy(Context context, String packageName,
+            int code) {
         // This is a workaround for R QPR, new API change is not allowed. We only allow the current
         // voice recognizer is also the voice interactor to noteproxy op.
         if (code != OP_RECORD_AUDIO) {
             return false;
         }
         final String voiceRecognitionComponent = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
-        final String voiceInteractionComponent = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
+                context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
 
         final String voiceRecognitionServicePackageName =
                 getComponentPackageNameFromString(voiceRecognitionComponent);
-        final String voiceInteractionServicePackageName =
-                getComponentPackageNameFromString(voiceInteractionComponent);
-        return Objects.equals(packageName, voiceRecognitionServicePackageName) && Objects.equals(
-                voiceRecognitionServicePackageName, voiceInteractionServicePackageName);
+        return (Objects.equals(packageName, voiceRecognitionServicePackageName))
+                && isPackagePreInstalled(context, packageName);
     }
 
-    private String getComponentPackageNameFromString(String from) {
+    private static String getComponentPackageNameFromString(String from) {
         ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null;
         return componentName != null ? componentName.getPackageName() : "";
     }
 
+    private static boolean isPackagePreInstalled(Context context, String packageName) {
+        try {
+            final PackageManager pm = context.getPackageManager();
+            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+            return ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     /**
      * Do a quick check for whether an application might be able to perform an operation.
      * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 9eeb9f6..ec7d783 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -36,8 +36,8 @@
 interface IUiAutomationConnection {
     void connect(IAccessibilityServiceClient client, int flags);
     void disconnect();
-    boolean injectInputEvent(in InputEvent event, boolean sync);
-    void syncInputTransactions();
+    boolean injectInputEvent(in InputEvent event, boolean sync, boolean waitForAnimations);
+    void syncInputTransactions(boolean waitForAnimations);
     boolean setRotation(int rotation);
     Bitmap takeScreenshot(in Rect crop);
     boolean clearWindowContentFrameStats(int windowId);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 3e249bb..e9d63d2 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1119,7 +1119,8 @@
         }
         try {
             WindowManagerGlobal.getWindowManagerService().injectInputAfterTransactionsApplied(event,
-                    InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+                    InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH,
+                    true /* waitForAnimations */);
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 75f7cec..2bf5368 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -67,6 +67,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -4887,12 +4888,6 @@
             mN.mUsesStandardHeader = false;
         }
 
-        private RemoteViews applyStandardTemplate(int resId, int viewType,
-                TemplateBindResult result) {
-            return applyStandardTemplate(resId,
-                    mParams.reset().viewType(viewType).fillTextsFrom(this), result);
-        }
-
         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
                 TemplateBindResult result) {
             p.headerless(resId == getBaseLayoutResource()
@@ -4906,7 +4901,7 @@
             bindNotificationHeader(contentView, p);
             bindLargeIconAndApplyMargin(contentView, p, result);
             boolean showProgress = handleProgressBar(contentView, ex, p);
-            if (p.title != null && p.title.length() > 0) {
+            if (p.title != null && p.title.length() > 0 && !p.mHasCustomContent) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
                 setTextViewColorPrimary(contentView, R.id.title, p);
@@ -5301,6 +5296,12 @@
                 contentView.setViewVisibility(R.id.app_name_text, View.GONE);
                 return false;
             }
+            if (p.mHeaderless && !p.mHasCustomContent) {
+                contentView.setViewVisibility(R.id.app_name_text, View.GONE);
+                // the headerless template will have the TITLE in this position; return true to
+                // keep the divider visible between that title and the next text element.
+                return true;
+            }
             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
             if (isColorized(p)) {
@@ -5348,14 +5349,11 @@
             big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
             big.setTextViewText(R.id.notification_material_reply_text_3, null);
 
+            final boolean snoozeEnabled = mContext.getContentResolver() != null
+                    && (Settings.Secure.getInt(mContext.getContentResolver(),
+                        Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1);
             big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
-                    R.dimen.notification_content_margin);
-        }
-
-        private RemoteViews applyStandardTemplateWithActions(int layoutId, int viewType,
-                TemplateBindResult result) {
-            return applyStandardTemplateWithActions(layoutId,
-                    mParams.reset().viewType(viewType).fillTextsFrom(this), result);
+                    snoozeEnabled ? 0 : R.dimen.notification_content_margin);
         }
 
         private static List<Notification.Action> filterOutContextualActions(
@@ -5495,8 +5493,10 @@
                     return styleView;
                 }
             }
-            return applyStandardTemplate(getBaseLayoutResource(),
-                    StandardTemplateParams.VIEW_TYPE_NORMAL, null /* result */);
+            StandardTemplateParams p = mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+                    .fillTextsFrom(this);
+            return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
         }
 
         private boolean useExistingRemoteView() {
@@ -5516,14 +5516,27 @@
                 result = mStyle.makeBigContentView();
                 hideLine1Text(result);
             }
-            if (result == null) {
-                result = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
-                        StandardTemplateParams.VIEW_TYPE_BIG, null /* result */);
+            if (result == null && bigContentViewRequired()) {
+                StandardTemplateParams p = mParams.reset()
+                        .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                        .fillTextsFrom(this);
+                result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
+                        null /* result */);
             }
             makeHeaderExpanded(result);
             return result;
         }
 
+        private boolean bigContentViewRequired() {
+            // If the big content view has no content, we can exempt the app from having to show it.
+            // TODO(b/173550917): add an UNDO style then force this requirement on apps targeting S
+            boolean exempt = mN.contentView != null && mN.bigContentView == null
+                    && mStyle == null && mActions.size() == 0
+                    && mN.extras.getCharSequence(EXTRA_TITLE) == null
+                    && mN.extras.getCharSequence(EXTRA_TEXT) == null;
+            return !exempt;
+        }
+
         /**
          * Construct a RemoteViews for the final notification header only. This will not be
          * colorized.
@@ -8689,18 +8702,24 @@
                return makeStandardTemplateWithCustomContent(headsUpContentView);
             }
             TemplateBindResult result = new TemplateBindResult();
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+                    .hasCustomContent(headsUpContentView != null)
+                    .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
-                    mBuilder.getHeadsUpBaseLayoutResource(),
-                    StandardTemplateParams.VIEW_TYPE_HEADS_UP, result);
+                    mBuilder.getHeadsUpBaseLayoutResource(), p, result);
             buildIntoRemoteViewContent(remoteViews, headsUpContentView, result, true);
             return remoteViews;
         }
 
         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
             TemplateBindResult result = new TemplateBindResult();
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+                    .hasCustomContent(customContent != null)
+                    .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
-                    mBuilder.getBaseLayoutResource(),
-                    StandardTemplateParams.VIEW_TYPE_NORMAL, result);
+                    mBuilder.getBaseLayoutResource(), p, result);
             buildIntoRemoteViewContent(remoteViews, customContent, result, true);
             return remoteViews;
         }
@@ -8710,9 +8729,12 @@
                     ? mBuilder.mN.contentView
                     : mBuilder.mN.bigContentView;
             TemplateBindResult result = new TemplateBindResult();
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .hasCustomContent(bigContentView != null)
+                    .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
-                    mBuilder.getBigBaseLayoutResource(),
-                    StandardTemplateParams.VIEW_TYPE_BIG, result);
+                    mBuilder.getBigBaseLayoutResource(), p, result);
             buildIntoRemoteViewContent(remoteViews, bigContentView, result, false);
             return remoteViews;
         }
@@ -11025,6 +11047,7 @@
 
         int mViewType = VIEW_TYPE_UNSPECIFIED;
         boolean mHeaderless;
+        boolean mHasCustomContent;
         boolean hasProgress = true;
         CharSequence title;
         CharSequence text;
@@ -11038,6 +11061,7 @@
         final StandardTemplateParams reset() {
             mViewType = VIEW_TYPE_UNSPECIFIED;
             mHeaderless = false;
+            mHasCustomContent = false;
             hasProgress = true;
             title = null;
             text = null;
@@ -11064,6 +11088,11 @@
             return this;
         }
 
+        final StandardTemplateParams hasCustomContent(boolean hasCustomContent) {
+            this.mHasCustomContent = hasCustomContent;
+            return this;
+        }
+
         final StandardTemplateParams title(CharSequence title) {
             this.title = title;
             return this;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index f76a757..9dbf1ff6 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -179,6 +179,12 @@
      * extras change, and don't care that any entities that received your
      * previous PendingIntent will be able to launch it with your new
      * extras even if they are not explicitly given to it.
+     *
+     * <p>{@link #FLAG_UPDATE_CURRENT} still works even if {@link
+     * #FLAG_IMMUTABLE} is set - the creator of the PendingIntent can always
+     * update the PendingIntent itself. The IMMUTABLE flag only limits the
+     * ability to alter the semantics of the intent that is sent by {@link
+     * #send} by the invoker of {@link #send}.
      */
     public static final int FLAG_UPDATE_CURRENT = 1<<27;
 
@@ -187,6 +193,11 @@
      * This means that the additional intent argument passed to the send
      * methods to fill in unpopulated properties of this intent will be
      * ignored.
+     *
+     * <p>{@link #FLAG_IMMUTABLE} only limits the ability to alter the
+     * semantics of the intent that is sent by {@link #send} by the invoker of
+     * {@link #send}. The creator of the PendingIntent can always update the
+     * PendingIntent itself via {@link #FLAG_UPDATE_CURRENT}.
      */
     public static final int FLAG_IMMUTABLE = 1<<26;
 
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index b0307d1..d2be8a4 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -33,10 +33,10 @@
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
-            "name": "CtsStatsdHostTestCases",
+            "name": "CtsStatsdAtomHostTestCases",
             "options": [
                 {
-                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testAppOps"
+                    "include-filter": "android.cts.statsdatom.appops.AppOpsTests#testAppOps"
                 }
             ]
         },
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 7fdf06f7..ca67dba 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -36,6 +36,7 @@
 import android.window.WindowContainerToken;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Stores information about a particular Task.
@@ -220,6 +221,12 @@
      */
     public Rect parentBounds;
 
+    /**
+     * Whether this task is focused.
+     * @hide
+     */
+    public boolean isFocused;
+
     TaskInfo() {
         // Do nothing
     }
@@ -288,6 +295,37 @@
     }
 
     /**
+      * Returns {@code true} if parameters that are important for task organizers have changed
+      * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners
+      * about that.
+      * @hide
+      */
+    public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) {
+        if (that == null) {
+            return false;
+        }
+        return topActivityType == that.topActivityType
+                && isResizeable == that.isResizeable
+                && Objects.equals(positionInParent, that.positionInParent)
+                && equalsLetterboxParams(that)
+                && pictureInPictureParams == that.pictureInPictureParams
+                && getWindowingMode() == that.getWindowingMode()
+                && Objects.equals(taskDescription, that.taskDescription)
+                && isFocused == that.isFocused;
+    }
+
+    private boolean equalsLetterboxParams(TaskInfo that) {
+        return Objects.equals(letterboxActivityBounds, that.letterboxActivityBounds)
+                && Objects.equals(
+                        getConfiguration().windowConfiguration.getBounds(),
+                        that.getConfiguration().windowConfiguration.getBounds())
+                && Objects.equals(
+                        getConfiguration().windowConfiguration.getMaxBounds(),
+                        that.getConfiguration().windowConfiguration.getMaxBounds())
+                && Objects.equals(parentBounds, that.parentBounds);
+    }
+
+    /**
      * Reads the TaskInfo from a parcel.
      */
     void readFromParcel(Parcel source) {
@@ -319,6 +357,7 @@
         positionInParent = source.readTypedObject(Point.CREATOR);
         parentTaskId = source.readInt();
         parentBounds = source.readTypedObject(Rect.CREATOR);
+        isFocused = source.readBoolean();
     }
 
     /**
@@ -354,6 +393,7 @@
         dest.writeTypedObject(positionInParent, flags);
         dest.writeInt(parentTaskId);
         dest.writeTypedObject(parentBounds, flags);
+        dest.writeBoolean(isFocused);
     }
 
     @Override
@@ -376,8 +416,9 @@
                 + " launchCookies" + launchCookies
                 + " letterboxActivityBounds=" + letterboxActivityBounds
                 + " positionInParent=" + positionInParent
-                + " parentTaskId: " + parentTaskId
-                + " parentBounds: " + parentBounds
+                + " parentTaskId=" + parentTaskId
+                + " parentBounds=" + parentBounds
+                + " isFocused=" + isFocused
                 + "}";
     }
 }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 1b0fd9e..787393e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -695,6 +695,9 @@
 
     /**
      * A method for injecting an arbitrary input event.
+     *
+     * This method waits for all window container animations and surface operations to complete.
+     *
      * <p>
      * <strong>Note:</strong> It is caller's responsibility to recycle the event.
      * </p>
@@ -704,12 +707,34 @@
      * @return Whether event injection succeeded.
      */
     public boolean injectInputEvent(InputEvent event, boolean sync) {
+        return injectInputEvent(event, sync, true /* waitForAnimations */);
+    }
+
+    /**
+     * A method for injecting an arbitrary input event, optionally waiting for window animations to
+     * complete.
+     * <p>
+     * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+     * </p>
+     *
+     * @param event The event to inject.
+     * @param sync  Whether to inject the event synchronously.
+     * @param waitForAnimations Whether to wait for all window container animations and surface
+     *   operations to complete.
+     * @return Whether event injection succeeded.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean injectInputEvent(@NonNull InputEvent event, boolean sync,
+            boolean waitForAnimations) {
         try {
             if (DEBUG) {
-                Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+                Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync + " waitForAnimations: "
+                        + waitForAnimations);
             }
             // Calling out without a lock held.
-            return mUiAutomationConnection.injectInputEvent(event, sync);
+            return mUiAutomationConnection.injectInputEvent(event, sync, waitForAnimations);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while injecting input event!", re);
         }
@@ -726,7 +751,26 @@
     public void syncInputTransactions() {
         try {
             // Calling out without a lock held.
-            mUiAutomationConnection.syncInputTransactions();
+            mUiAutomationConnection.syncInputTransactions(true /* waitForAnimations */);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while syncing input transactions!", re);
+        }
+    }
+
+    /**
+     * A request for WindowManagerService to wait until all input information has been sent from
+     * WindowManager to native InputManager and optionally wait for animations to complete.
+     *
+     * @param waitForAnimations Whether to wait for all window container animations and surface
+     *   operations to complete.
+     *
+     * @hide
+     */
+    @TestApi
+    public void syncInputTransactions(boolean waitForAnimations) {
+        try {
+            // Calling out without a lock held.
+            mUiAutomationConnection.syncInputTransactions(waitForAnimations);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while syncing input transactions!", re);
         }
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 290e121..7036b6e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -124,7 +124,7 @@
     }
 
     @Override
-    public boolean injectInputEvent(InputEvent event, boolean sync) {
+    public boolean injectInputEvent(InputEvent event, boolean sync, boolean waitForAnimations) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
@@ -134,7 +134,8 @@
                 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
         final long identity = Binder.clearCallingIdentity();
         try {
-            return mWindowManager.injectInputAfterTransactionsApplied(event, mode);
+            return mWindowManager.injectInputAfterTransactionsApplied(event, mode,
+                    waitForAnimations);
         } catch (RemoteException e) {
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -143,7 +144,7 @@
     }
 
     @Override
-    public void syncInputTransactions() {
+    public void syncInputTransactions(boolean waitForAnimations) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
@@ -151,12 +152,11 @@
         }
 
         try {
-            mWindowManager.syncInputTransactions();
+            mWindowManager.syncInputTransactions(waitForAnimations);
         } catch (RemoteException e) {
         }
     }
 
-
     @Override
     public boolean setRotation(int rotation) {
         synchronized (mLock) {
diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java
index d65be9b..77891e0 100644
--- a/core/java/android/app/WaitResult.java
+++ b/core/java/android/app/WaitResult.java
@@ -40,14 +40,21 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"LAUNCH_STATE_"}, value = {
+            LAUNCH_STATE_UNKNOWN,
             LAUNCH_STATE_COLD,
             LAUNCH_STATE_WARM,
-            LAUNCH_STATE_HOT
+            LAUNCH_STATE_HOT,
+            LAUNCH_STATE_RELAUNCH
     })
     public @interface LaunchState {
     }
 
     /**
+     * Not considered as a launch event, e.g. the activity is already on top.
+     */
+    public static final int LAUNCH_STATE_UNKNOWN = 0;
+
+    /**
      * Cold launch sequence: a new process has started.
      */
     public static final int LAUNCH_STATE_COLD = 1;
@@ -62,6 +69,13 @@
      */
     public static final int LAUNCH_STATE_HOT = 3;
 
+    /**
+     * Relaunch launch sequence: process reused, but activity has to be destroyed and created.
+     * E.g. the current device configuration is different from the background activity that will be
+     * brought to foreground, and the activity doesn't declare to handle the change.
+     */
+    public static final int LAUNCH_STATE_RELAUNCH = 4;
+
     public static final int INVALID_DELAY = -1;
     public int result;
     public boolean timeout;
@@ -124,6 +138,8 @@
                 return "WARM";
             case LAUNCH_STATE_HOT:
                 return "HOT";
+            case LAUNCH_STATE_RELAUNCH:
+                return "RELAUNCH";
             default:
                 return "UNKNOWN (" + type + ")";
         }
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
new file mode 100644
index 0000000..f5674e5
--- /dev/null
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * The People Space tile contains all relevant information to render a tile in People Space: namely
+ * the data of any visible conversation notification associated, associated statuses, and the last
+ * interaction time.
+ *
+ * @hide
+ */
+public class PeopleSpaceTile implements Parcelable {
+
+    private String mId;
+    private CharSequence mUserName;
+    private Icon mUserIcon;
+    private int mUid;
+    private Uri mContactUri;
+    private String mPackageName;
+    private long mLastInteractionTimestamp;
+    private boolean mIsImportantConversation;
+    private boolean mIsHiddenConversation;
+    private StatusBarNotification mNotification;
+    private Intent mIntent;
+    // TODO: add a List of the Status objects once created
+
+    private PeopleSpaceTile(Builder b) {
+        mId = b.mId;
+        mUserName = b.mUserName;
+        mUserIcon = b.mUserIcon;
+        mContactUri = b.mContactUri;
+        mUid = b.mUid;
+        mPackageName = b.mPackageName;
+        mLastInteractionTimestamp = b.mLastInteractionTimestamp;
+        mIsImportantConversation = b.mIsImportantConversation;
+        mIsHiddenConversation = b.mIsHiddenConversation;
+        mNotification = b.mNotification;
+        mIntent = b.mIntent;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public CharSequence getUserName() {
+        return mUserName;
+    }
+
+    public Icon getUserIcon() {
+        return mUserIcon;
+    }
+
+    /** Returns the Uri associated with the user in Android Contacts database. */
+    public Uri getContactUri() {
+        return mContactUri;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns the timestamp of the last interaction. */
+    public long getLastInteractionTimestamp() {
+        return mLastInteractionTimestamp;
+    }
+
+    /**
+     * Whether the conversation is important.
+     */
+    public boolean isImportantConversation() {
+        return mIsImportantConversation;
+    }
+
+    /**
+     * Whether the conversation should be hidden.
+     */
+    public boolean isHiddenConversation() {
+        return mIsHiddenConversation;
+    }
+
+    /**
+     * If a notification is currently active that maps to the relevant shortcut ID, provides the
+     * {@link StatusBarNotification} associated.
+     */
+    public StatusBarNotification getNotification() {
+        return mNotification;
+    }
+
+    /**
+     * Provides an intent to launch. If present, we should manually launch the intent on tile
+     * click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID.
+     *
+     * <p>This field should only be used if manually constructing a tile without an associated
+     * shortcut to launch (i.e. birthday tiles).
+     */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /** Builder to create a {@link PeopleSpaceTile}. */
+    public static class Builder {
+        private String mId;
+        private CharSequence mUserName;
+        private Icon mUserIcon;
+        private Uri mContactUri;
+        private int mUid;
+        private String mPackageName;
+        private long mLastInteractionTimestamp;
+        private boolean mIsImportantConversation;
+        private boolean mIsHiddenConversation;
+        private StatusBarNotification mNotification;
+        private Intent mIntent;
+
+        /** Builder for use only if a shortcut is not available for the tile. */
+        public Builder(String id, String userName, Icon userIcon, Intent intent) {
+            mId = id;
+            mUserName = userName;
+            mUserIcon = userIcon;
+            mIntent = intent;
+            mPackageName = intent == null ? null : intent.getPackage();
+        }
+
+        public Builder(ShortcutInfo info) {
+            mId = info.getId();
+            mUserName = info.getLabel();
+            mUserIcon = info.getIcon();
+            mUid = info.getUserId();
+            mPackageName = info.getPackage();
+        }
+
+        /** Sets the ID for the tile. */
+        public Builder setId(String id) {
+            mId = id;
+            return this;
+        }
+
+        /** Sets the user name. */
+        public Builder setUserName(CharSequence userName) {
+            mUserName = userName;
+            return this;
+        }
+
+        /** Sets the icon shown for the user. */
+        public Builder setUserIcon(Icon userIcon) {
+            mUserIcon = userIcon;
+            return this;
+        }
+
+        /** Sets the Uri associated with the user in Android Contacts database. */
+        public Builder setContactUri(Uri uri) {
+            mContactUri = uri;
+            return this;
+        }
+
+        /** Sets the associated uid. */
+        public Builder setUid(int uid) {
+            mUid = uid;
+            return this;
+        }
+
+        /** Sets the package shown that provided the information. */
+        public Builder setPackageName(String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        /** Sets the last interaction timestamp. */
+        public Builder setLastInteractionTimestamp(long lastInteractionTimestamp) {
+            mLastInteractionTimestamp = lastInteractionTimestamp;
+            return this;
+        }
+
+        /** Sets whether the conversation is important. */
+        public Builder setIsImportantConversation(boolean isImportantConversation) {
+            mIsImportantConversation = isImportantConversation;
+            return this;
+        }
+
+        /** Sets whether the conversation is hidden. */
+        public Builder setIsHiddenConversation(boolean isHiddenConversation) {
+            mIsHiddenConversation = isHiddenConversation;
+            return this;
+        }
+
+        /** Sets the associated notification. */
+        public Builder setNotification(StatusBarNotification notification) {
+            mNotification = notification;
+            return this;
+        }
+
+        /** Sets an intent to launch on click. */
+        public Builder setIntent(Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /** Builds a {@link PeopleSpaceTile}. */
+        @NonNull
+        public PeopleSpaceTile build() {
+            return new PeopleSpaceTile(this);
+        }
+    }
+
+    private PeopleSpaceTile(Parcel in) {
+        mId = in.readString();
+        mUserName = in.readCharSequence();
+        mUserIcon = in.readParcelable(Icon.class.getClassLoader());
+        mUid = in.readInt();
+        mPackageName = in.readString();
+        mLastInteractionTimestamp = in.readLong();
+        mIsImportantConversation = in.readBoolean();
+        mIsHiddenConversation = in.readBoolean();
+        mNotification = in.readParcelable(StatusBarNotification.class.getClassLoader());
+        mIntent = in.readParcelable(Intent.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeCharSequence(mUserName);
+        dest.writeParcelable(mUserIcon, flags);
+        dest.writeInt(mUid);
+        dest.writeString(mPackageName);
+        dest.writeLong(mLastInteractionTimestamp);
+        dest.writeParcelable(mNotification, flags);
+        dest.writeBoolean(mIsImportantConversation);
+        dest.writeBoolean(mIsHiddenConversation);
+        dest.writeParcelable(mIntent, flags);
+    }
+
+    public static final @android.annotation.NonNull
+            Creator<PeopleSpaceTile> CREATOR = new Creator<PeopleSpaceTile>() {
+                public PeopleSpaceTile createFromParcel(Parcel source) {
+                    return new PeopleSpaceTile(source);
+                }
+
+                public PeopleSpaceTile[] newArray(int size) {
+                    return new PeopleSpaceTile[size];
+                }
+            };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 37e0a44..6a3f6b4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -63,6 +63,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.StatFs;
+import android.os.StrictMode;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -6285,4 +6286,25 @@
     public boolean isUiContext() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
+
+    /**
+     * Returns {@code true} if the context is a UI context which can access UI components such as
+     * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or
+     * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI
+     * contexts throws {@link android.os.strictmode.Violation} if
+     * {@link StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
+     * <p>
+     * Examples of UI contexts are
+     * an {@link android.app.Activity Activity}, a context created from
+     * {@link #createWindowContext(int, Bundle)} or
+     * {@link android.inputmethodservice.InputMethodService InputMethodService}
+     * </p>
+     *
+     * @see #getDisplay()
+     * @see #getSystemService(String)
+     * @see StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
+     */
+    public static boolean isUiContext(@NonNull Context context) {
+        return context.isUiContext();
+    }
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c964b4b..4b7bbbf 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -433,6 +433,16 @@
          */
         public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
 
+        /**
+         * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}.
+         *
+         * <p>The caller must have the system {@code ACCESS_SHORTCUTS} permission.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final int FLAG_GET_PERSONS_DATA = 1 << 11;
+
         /** @hide */
         @IntDef(flag = true, prefix = { "FLAG_" }, value = {
                 FLAG_MATCH_DYNAMIC,
@@ -440,6 +450,7 @@
                 FLAG_MATCH_MANIFEST,
                 FLAG_MATCH_CACHED,
                 FLAG_GET_KEY_FIELDS_ONLY,
+                FLAG_GET_PERSONS_DATA,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface QueryFlags {}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index cc484de..9e0c30a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -64,6 +64,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -75,6 +77,7 @@
 import android.util.AndroidException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import dalvik.system.VMRuntime;
@@ -116,6 +119,213 @@
     }
 
     /**
+     * A property value set within the manifest.
+     * <p>
+     * The value of a property will only have a single type, as defined by
+     * the property itself.
+     */
+    public static final class Property implements Parcelable {
+        private static final int TYPE_BOOLEAN = 1;
+        private static final int TYPE_FLOAT = 2;
+        private static final int TYPE_INTEGER = 3;
+        private static final int TYPE_RESOURCE = 4;
+        private static final int TYPE_STRING = 5;
+        private final String mName;
+        private boolean mBooleanValue;
+        private float mFloatValue;
+        private int mIntegerValue;
+        private String mStringValue;
+        private final int mType;
+
+        /** @hide */
+        @VisibleForTesting
+        public Property(@NonNull String name, int type) {
+            assert name != null;
+            assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
+            this.mName = name;
+            this.mType = type;
+        }
+        /** @hide */
+        public Property(@NonNull String name, boolean value) {
+            this(name, TYPE_BOOLEAN);
+            mBooleanValue = value;
+        }
+        /** @hide */
+        public Property(@NonNull String name, float value) {
+            this(name, TYPE_FLOAT);
+            mFloatValue = value;
+        }
+        /** @hide */
+        public Property(@NonNull String name, int value, boolean isResource) {
+            this(name, isResource ? TYPE_RESOURCE : TYPE_INTEGER);
+            mIntegerValue = value;
+        }
+        /** @hide */
+        public Property(@NonNull String name, String value) {
+            this(name, TYPE_STRING);
+            mStringValue = value;
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Returns the name of the property.
+         */
+        @NonNull public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the boolean value set for the property.
+         * <p>If the property is not of a boolean type, returns {@code false}.
+         */
+        public boolean getBoolean() {
+            return mBooleanValue;
+        }
+
+        /**
+         * Returns {@code true} if the property is a boolean type. Otherwise {@code false}.
+         */
+        public boolean isBoolean() {
+            return mType == TYPE_BOOLEAN;
+        }
+
+        /**
+         * Returns the float value set for the property.
+         * <p>If the property is not of a float type, returns {@code 0.0}.
+         */
+        public float getFloat() {
+            return mFloatValue;
+        }
+
+        /**
+         * Returns {@code true} if the property is a float type. Otherwise {@code false}.
+         */
+        public boolean isFloat() {
+            return mType == TYPE_FLOAT;
+        }
+
+        /**
+         * Returns the integer value set for the property.
+         * <p>If the property is not of an integer type, returns {@code 0}.
+         */
+        public int getInteger() {
+            return mType == TYPE_INTEGER ? mIntegerValue : 0;
+        }
+
+        /**
+         * Returns {@code true} if the property is an integer type. Otherwise {@code false}.
+         */
+        public boolean isInteger() {
+            return mType == TYPE_INTEGER;
+        }
+
+        /**
+         * Returns the a resource id set for the property.
+         * <p>If the property is not of a resource id type, returns {@code 0}.
+         */
+        public int getResourceId() {
+            return mType == TYPE_RESOURCE ? mIntegerValue : 0;
+        }
+
+        /**
+         * Returns {@code true} if the property is a resource id type. Otherwise {@code false}.
+         */
+        public boolean isResourceId() {
+            return mType == TYPE_RESOURCE;
+        }
+
+        /**
+         * Returns the a String value set for the property.
+         * <p>If the property is not a String type, returns {@code null}.
+         */
+        @Nullable public String getString() {
+            return mStringValue;
+        }
+
+        /**
+         * Returns {@code true} if the property is a String type. Otherwise {@code false}.
+         */
+        public boolean isString() {
+            return mType == TYPE_STRING;
+        }
+
+        /**
+         * Adds a mapping from the given key to this property's value in the provided
+         * {@link android.os.Bundle}. If the provided {@link android.os.Bundle} is
+         * {@code null}, creates a new {@link android.os.Bundle}.
+         * @hide
+         */
+        public Bundle toBundle(Bundle outBundle) {
+            final Bundle b = outBundle == null ? new Bundle() : outBundle;
+            if (mType == TYPE_BOOLEAN) {
+                b.putBoolean(mName, mBooleanValue);
+            } else if (mType == TYPE_FLOAT) {
+                b.putFloat(mName, mFloatValue);
+            } else if (mType == TYPE_INTEGER) {
+                b.putInt(mName, mIntegerValue);
+            } else if (mType == TYPE_RESOURCE) {
+                b.putInt(mName, mIntegerValue);
+            } else if (mType == TYPE_STRING) {
+                b.putString(mName, mStringValue);
+            }
+            return b;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mName);
+            dest.writeInt(mType);
+            if (mType == TYPE_BOOLEAN) {
+                dest.writeBoolean(mBooleanValue);
+            } else if (mType == TYPE_FLOAT) {
+                dest.writeFloat(mFloatValue);
+            } else if (mType == TYPE_INTEGER) {
+                dest.writeInt(mIntegerValue);
+            } else if (mType == TYPE_RESOURCE) {
+                dest.writeInt(mIntegerValue);
+            } else if (mType == TYPE_STRING) {
+                dest.writeString(mStringValue);
+            }
+        }
+
+        @NonNull
+        public static final Creator<Property> CREATOR = new Creator<Property>() {
+            @Override
+            public Property createFromParcel(@NonNull Parcel source) {
+                final String name = source.readString();
+                final int type = source.readInt();
+                if (type == TYPE_BOOLEAN) {
+                    return new Property(name, source.readBoolean());
+                } else if (type == TYPE_FLOAT) {
+                    return new Property(name, source.readFloat());
+                } else if (type == TYPE_INTEGER) {
+                    return new Property(name, source.readInt(), false);
+                } else if (type == TYPE_RESOURCE) {
+                    return new Property(name, source.readInt(), true);
+                } else if (type == TYPE_STRING) {
+                    return new Property(name, source.readString());
+                }
+                return null;
+            }
+
+            @Override
+            public Property[] newArray(int size) {
+                return new Property[size];
+            }
+        };
+    }
+
+    /**
      * Listener for changes in permissions granted to a UID.
      *
      * @hide
@@ -8124,7 +8334,7 @@
      * Trust any Installer to provide checksums for the package.
      * @see #requestChecksums
      */
-    public static final @Nullable List<Certificate> TRUST_ALL = Collections.singletonList(null);
+    public static final @NonNull List<Certificate> TRUST_ALL = Collections.singletonList(null);
 
     /**
      * Don't trust any Installer to provide checksums for the package.
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
new file mode 100644
index 0000000..f34e2bf
--- /dev/null
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * The camera action state used for passing camera usage information from
+ * camera service to camera service proxy .
+ *
+ * Include camera id, facing, state, client apk name, apiLevel, isNdk,
+ * and session/stream statistics.
+ *
+ * @hide
+ */
+public class CameraSessionStats implements Parcelable {
+    public static final int CAMERA_STATE_OPEN = 0;
+    public static final int CAMERA_STATE_ACTIVE = 1;
+    public static final int CAMERA_STATE_IDLE = 2;
+    public static final int CAMERA_STATE_CLOSED = 3;
+
+    /**
+     * Values for notifyCameraState facing
+     */
+    public static final int CAMERA_FACING_BACK = 0;
+    public static final int CAMERA_FACING_FRONT = 1;
+    public static final int CAMERA_FACING_EXTERNAL = 2;
+
+    /**
+     * Values for notifyCameraState api level
+     */
+    public static final int CAMERA_API_LEVEL_1 = 1;
+    public static final int CAMERA_API_LEVEL_2 = 2;
+
+    private String mCameraId;
+    private int mFacing;
+    private int mNewCameraState;
+    private String mClientName;
+    private int mApiLevel;
+    private boolean mIsNdk;
+    private int mLatencyMs;
+    private int mSessionType;
+    private int mInternalReconfigure;
+    private long mRequestCount;
+    private long mResultErrorCount;
+    private boolean mDeviceError;
+    private ArrayList<CameraStreamStats> mStreamStats;
+
+    public CameraSessionStats() {
+        mFacing = -1;
+        mNewCameraState = -1;
+        mApiLevel = -1;
+        mIsNdk = false;
+        mLatencyMs = -1;
+        mSessionType = -1;
+        mInternalReconfigure = -1;
+        mRequestCount = 0;
+        mResultErrorCount = 0;
+        mDeviceError = false;
+        mStreamStats = new ArrayList<CameraStreamStats>();
+    }
+
+    public CameraSessionStats(String cameraId, int facing, int newCameraState,
+            String clientName, int apiLevel, boolean isNdk, int creationDuration,
+            int sessionType, int internalReconfigure) {
+        mCameraId = cameraId;
+        mFacing = facing;
+        mNewCameraState = newCameraState;
+        mClientName = clientName;
+        mApiLevel = apiLevel;
+        mIsNdk = isNdk;
+        mLatencyMs = creationDuration;
+        mSessionType = sessionType;
+        mInternalReconfigure = internalReconfigure;
+        mStreamStats = new ArrayList<CameraStreamStats>();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CameraSessionStats> CREATOR =
+            new Parcelable.Creator<CameraSessionStats>() {
+        @Override
+        public CameraSessionStats createFromParcel(Parcel in) {
+            return new CameraSessionStats(in);
+        }
+
+        @Override
+        public CameraSessionStats[] newArray(int size) {
+            return new CameraSessionStats[size];
+        }
+    };
+
+    private CameraSessionStats(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mCameraId);
+        dest.writeInt(mFacing);
+        dest.writeInt(mNewCameraState);
+        dest.writeString(mClientName);
+        dest.writeInt(mApiLevel);
+        dest.writeBoolean(mIsNdk);
+        dest.writeInt(mLatencyMs);
+        dest.writeInt(mSessionType);
+        dest.writeInt(mInternalReconfigure);
+        dest.writeLong(mRequestCount);
+        dest.writeLong(mResultErrorCount);
+        dest.writeBoolean(mDeviceError);
+        dest.writeTypedList(mStreamStats);
+    }
+
+    public void readFromParcel(Parcel in) {
+        mCameraId = in.readString();
+        mFacing = in.readInt();
+        mNewCameraState = in.readInt();
+        mClientName = in.readString();
+        mApiLevel = in.readInt();
+        mIsNdk = in.readBoolean();
+        mLatencyMs = in.readInt();
+        mSessionType = in.readInt();
+        mInternalReconfigure = in.readInt();
+        mRequestCount = in.readLong();
+        mResultErrorCount = in.readLong();
+        mDeviceError = in.readBoolean();
+
+        ArrayList<CameraStreamStats> streamStats = new ArrayList<CameraStreamStats>();
+        in.readTypedList(streamStats, CameraStreamStats.CREATOR);
+        mStreamStats = streamStats;
+    }
+
+    public String getCameraId() {
+        return mCameraId;
+    }
+
+    public int getFacing() {
+        return mFacing;
+    }
+
+    public int getNewCameraState() {
+        return mNewCameraState;
+    }
+
+    public String getClientName() {
+        return mClientName;
+    }
+
+    public int getApiLevel() {
+        return mApiLevel;
+    }
+
+    public boolean isNdk() {
+        return mIsNdk;
+    }
+
+    public int getLatencyMs() {
+        return mLatencyMs;
+    }
+
+    public int getSessionType() {
+        return mSessionType;
+    }
+
+    public int getInternalReconfigureCount() {
+        return mInternalReconfigure;
+    }
+
+    public long getRequestCount() {
+        return mRequestCount;
+    }
+
+    public long getResultErrorCount() {
+        return mResultErrorCount;
+    }
+
+    public boolean getDeviceErrorFlag() {
+        return mDeviceError;
+    }
+
+    public List<CameraStreamStats> getStreamStats() {
+        return mStreamStats;
+    }
+}
diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java
new file mode 100644
index 0000000..ae801b6
--- /dev/null
+++ b/core/java/android/hardware/CameraStreamStats.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+/**
+ * The camera stream statistics used for passing camera stream information from
+ * camera service to camera service proxy.
+ *
+ * Include camera stream configuration, request/error counts, startup latency,
+ * and latency/jitter histograms.
+ *
+ * @hide
+ */
+public class CameraStreamStats implements Parcelable {
+
+    private int mWidth;
+    private int mHeight;
+    private int mFormat;
+    private int mDataSpace;
+    private long mUsage;
+    private long mRequestCount;
+    private long mErrorCount;
+    private int mStartLatencyMs;
+    private int mMaxHalBuffers;
+    private int mMaxAppBuffers;
+
+    private static final String TAG = "CameraStreamStats";
+
+    public CameraStreamStats() {
+        mWidth = 0;
+        mHeight = 0;
+        mFormat = 0;
+        mDataSpace = 0;
+        mUsage = 0;
+        mRequestCount = 0;
+        mErrorCount = 0;
+        mStartLatencyMs = 0;
+        mMaxHalBuffers = 0;
+        mMaxAppBuffers = 0;
+    }
+
+    public CameraStreamStats(int width, int height, int format,
+            int dataSpace, long usage, long requestCount, long errorCount,
+            int startLatencyMs, int maxHalBuffers, int maxAppBuffers) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = format;
+        mDataSpace = dataSpace;
+        mUsage = usage;
+        mRequestCount = requestCount;
+        mErrorCount = errorCount;
+        mStartLatencyMs = startLatencyMs;
+        mMaxHalBuffers = maxHalBuffers;
+        mMaxAppBuffers = maxAppBuffers;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CameraStreamStats> CREATOR =
+            new Parcelable.Creator<CameraStreamStats>() {
+        @Override
+        public CameraStreamStats createFromParcel(Parcel in) {
+            try {
+                CameraStreamStats streamStats = new CameraStreamStats(in);
+                return streamStats;
+            } catch (Exception e) {
+                Log.e(TAG, "Exception creating CameraStreamStats from parcel", e);
+                return null;
+            }
+        }
+
+        @Override
+        public CameraStreamStats[] newArray(int size) {
+            return new CameraStreamStats[size];
+        }
+    };
+
+    private CameraStreamStats(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        dest.writeInt(mFormat);
+        dest.writeInt(mDataSpace);
+        dest.writeLong(mUsage);
+        dest.writeLong(mRequestCount);
+        dest.writeLong(mErrorCount);
+        dest.writeInt(mStartLatencyMs);
+        dest.writeInt(mMaxHalBuffers);
+        dest.writeInt(mMaxAppBuffers);
+    }
+
+    public void readFromParcel(Parcel in) {
+        mWidth = in.readInt();
+        mHeight = in.readInt();
+        mFormat = in.readInt();
+        mDataSpace = in.readInt();
+        mUsage = in.readLong();
+        mRequestCount = in.readLong();
+        mErrorCount = in.readLong();
+        mStartLatencyMs = in.readInt();
+        mMaxHalBuffers = in.readInt();
+        mMaxAppBuffers = in.readInt();
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public int getFormat() {
+        return mFormat;
+    }
+
+    public int getDataSpace() {
+        return mDataSpace;
+    }
+
+    public long getUsage() {
+        return mUsage;
+    }
+
+    public long getRequestCount() {
+        return mRequestCount;
+    }
+
+    public long getErrorCount() {
+        return mErrorCount;
+    }
+
+    public int getStartLatencyMs() {
+        return mStartLatencyMs;
+    }
+
+    public int getMaxHalBuffers() {
+        return mMaxHalBuffers;
+    }
+
+    public int getMaxAppBuffers() {
+        return mMaxAppBuffers;
+    }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index b6f4bd3..9a9163c 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.utils.TaskSingleDrainer;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.Surface;
 
@@ -1002,7 +1003,7 @@
                     // begin transition to unconfigured
                     mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
                             /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
-                            /*sessionParams*/ null);
+                            /*sessionParams*/ null, SystemClock.uptimeMillis());
                 } catch (CameraAccessException e) {
                     // OK: do not throw checked exceptions.
                     Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 819d966..f564ad7 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
@@ -374,7 +375,8 @@
             outputConfigs.add(new OutputConfiguration(s));
         }
         configureStreamsChecked(/*inputConfig*/null, outputConfigs,
-                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
+                /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null,
+                SystemClock.uptimeMillis());
 
     }
 
@@ -395,12 +397,15 @@
      * @param operatingMode If the stream configuration is for a normal session,
      *     a constrained high speed session, or something else.
      * @param sessionParams Session parameters.
+     * @param createSessionStartTimeMs The timestamp when session creation starts, measured by
+     *     uptimeMillis().
      * @return whether or not the configuration was successful
      *
      * @throws CameraAccessException if there were any unexpected problems during configuration
      */
     public boolean configureStreamsChecked(InputConfiguration inputConfig,
-            List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
+            List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams,
+            long createSessionStartTime)
                     throws CameraAccessException {
         // Treat a null input the same an empty list
         if (outputs == null) {
@@ -479,9 +484,10 @@
                 int offlineStreamIds[];
                 if (sessionParams != null) {
                     offlineStreamIds = mRemoteDevice.endConfigure(operatingMode,
-                            sessionParams.getNativeCopy());
+                            sessionParams.getNativeCopy(), createSessionStartTime);
                 } else {
-                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null);
+                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null,
+                            createSessionStartTime);
                 }
 
                 mOfflineSupport.clear();
@@ -650,6 +656,7 @@
             List<OutputConfiguration> outputConfigurations,
             CameraCaptureSession.StateCallback callback, Executor executor,
             int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
+        long createSessionStartTime = SystemClock.uptimeMillis();
         synchronized(mInterfaceLock) {
             if (DEBUG) {
                 Log.d(TAG, "createCaptureSessionInternal");
@@ -677,7 +684,7 @@
             try {
                 // configure streams and then block until IDLE
                 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
-                        operatingMode, sessionParams);
+                        operatingMode, sessionParams, createSessionStartTime);
                 if (configureSuccess == true && inputConfig != null) {
                     input = mRemoteDevice.getInputSurface();
                 }
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index fa7301b..ba4395f 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -110,11 +110,11 @@
         }
     }
 
-    public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams)
-           throws CameraAccessException {
+    public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams,
+            long startTimeMs) throws CameraAccessException {
         try {
             return mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
-                    new CameraMetadataNative() : sessionParams);
+                    new CameraMetadataNative() : sessionParams, startTimeMs);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
             throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index defcab7..5a03ade 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -65,6 +65,12 @@
     public abstract boolean isProximitySensorAvailable();
 
     /**
+     * Returns the id of the {@link com.android.server.display.DisplayGroup} to which the provided
+     * display belongs.
+     */
+    public abstract int getDisplayGroupId(int displayId);
+
+    /**
      * 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/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f1534d9..eef4089 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -832,6 +832,22 @@
     }
 
     /**
+     * For CEC source devices (OTT/STB/Audio system): toggle the power status of the HDMI-connected
+     * display and follow the display's new power status.
+     * For all other devices: no functionality.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void toggleAndFollowTvPower() {
+        try {
+            mService.toggleAndFollowTvPower();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Controls whether volume control commands via HDMI CEC are enabled.
      *
      * <p>When disabled:
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index fab56b8..202e090 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -67,6 +67,11 @@
         }
 
         @Override
+        public void toggleAndFollowTvPower() {
+            HdmiControlServiceWrapper.this.toggleAndFollowTvPower();
+        }
+
+        @Override
         public void queryDisplayStatus(IHdmiControlCallback callback) {
             HdmiControlServiceWrapper.this.queryDisplayStatus(callback);
         }
@@ -360,6 +365,9 @@
     public void oneTouchPlay(IHdmiControlCallback callback) {}
 
     /** @hide */
+    public void toggleAndFollowTvPower() {}
+
+    /** @hide */
     public void queryDisplayStatus(IHdmiControlCallback callback) {}
 
     /** @hide */
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index af9d3ac..6d0c688 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -42,6 +42,7 @@
     int[] getSupportedTypes();
     HdmiDeviceInfo getActiveSource();
     void oneTouchPlay(IHdmiControlCallback callback);
+    void toggleAndFollowTvPower();
     void queryDisplayStatus(IHdmiControlCallback callback);
     void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
     void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 923b9b76..81e6e78 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -81,8 +81,10 @@
     private final transient boolean mParcelSensitiveFields;
 
     private static final int MIN_MTU    = 68;
-    /* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */
-    static final int MIN_MTU_V6 = 1280;
+
+    /** @hide */
+    public static final int MIN_MTU_V6 = 1280;
+
     private static final int MAX_MTU    = 10000;
 
     private static final int INET6_ADDR_LENGTH = 16;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index f806b56..40bb8bf 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -339,10 +339,14 @@
     public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
 
     /**
+     * Indicates that this network is temporarily unmetered.
+     * <p>
      * This capability will be set for networks that are generally metered, but are currently
      * unmetered, e.g., because the user is in a particular area. This capability can be changed at
      * any time. When it is removed, applications are responsible for stopping any data transfer
      * that should not occur on a metered network.
+     * Note that most apps should use {@link #NET_CAPABILITY_NOT_METERED} instead. For more
+     * information, see https://developer.android.com/about/versions/11/features/5g#meteredness.
      */
     public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
 
@@ -370,8 +374,8 @@
             | (1 << NET_CAPABILITY_FOREGROUND)
             | (1 << NET_CAPABILITY_NOT_CONGESTED)
             | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED);
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -1802,20 +1806,26 @@
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
 
-        if (mAdministratorUids.length == 0) {
-            sb.append(" AdministratorUids: ").append(Arrays.toString(mAdministratorUids));
+        if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+            sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
+        }
+
+        if (mRequestorUid != Process.INVALID_UID) {
+            sb.append(" RequestorUid: ").append(mRequestorUid);
+        }
+
+        if (mRequestorPackageName != null) {
+            sb.append(" RequestorPkg: ").append(mRequestorPackageName);
         }
 
         if (null != mSSID) {
             sb.append(" SSID: ").append(mSSID);
         }
 
-        if (mPrivateDnsBroken) {
-            sb.append(" Private DNS is broken");
-        }
 
-        sb.append(" RequestorUid: ").append(mRequestorUid);
-        sb.append(" RequestorPackageName: ").append(mRequestorPackageName);
+        if (mPrivateDnsBroken) {
+            sb.append(" PrivateDnsBroken");
+        }
 
         sb.append("]");
         return sb.toString();
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
new file mode 100644
index 0000000..8160edc
--- /dev/null
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import android.annotation.NonNull;
+
+/**
+ * This class represents a configuration for a connection to a Virtual Carrier Network gateway.
+ *
+ * <p>Each VcnGatewayConnectionConfig represents a single logical connection to a carrier gateway,
+ * and may provide one or more telephony services (as represented by network capabilities). Each
+ * gateway is expected to provide mobility for a given session as the device roams across {@link
+ * Network}s.
+ *
+ * <p>A VCN connection based on this configuration will be brought up dynamically based on device
+ * settings, and filed NetworkRequests. Underlying networks will be selected based on the services
+ * required by this configuration (as represented by network capabilities), and must be part of the
+ * subscription group under which this configuration is registered (see {@link
+ * VcnManager#setVcnConfig}).
+ *
+ * <p>Services that can be provided by a VCN network, or required for underlying networks are
+ * limited to services provided by cellular networks:
+ *
+ * <ul>
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
+ *   <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ * </ul>
+ *
+ * @hide
+ */
+public final class VcnGatewayConnectionConfig {
+    private VcnGatewayConnectionConfig() {
+        validate();
+    }
+
+    // TODO: Implement getters, validators, etc
+
+    /**
+     * Validates this configuration
+     *
+     * @hide
+     */
+    private void validate() {
+        // TODO: implement validation logic
+    }
+
+    // Parcelable methods
+
+    /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects */
+    public static class Builder {
+        // TODO: Implement this builder
+
+        /**
+         * Builds and validates the VcnGatewayConnectionConfig
+         *
+         * @return an immutable VcnGatewayConnectionConfig instance
+         */
+        @NonNull
+        public VcnGatewayConnectionConfig build() {
+            return new VcnGatewayConnectionConfig();
+        }
+    }
+}
diff --git a/core/java/android/os/BasicShellCommandHandler.java b/core/java/android/os/BasicShellCommandHandler.java
deleted file mode 100644
index 366da3d..0000000
--- a/core/java/android/os/BasicShellCommandHandler.java
+++ /dev/null
@@ -1,342 +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 android.os;
-
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-/**
- * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
- * be copied into mainline modules, so this class must not use any hidden APIs.
- *
- * @hide
- */
-public abstract class BasicShellCommandHandler {
-    static final String TAG = "ShellCommand";
-    static final boolean DEBUG = false;
-
-    private Binder mTarget;
-    private FileDescriptor mIn;
-    private FileDescriptor mOut;
-    private FileDescriptor mErr;
-    private String[] mArgs;
-
-    private String mCmd;
-    private int mArgPos;
-    private String mCurArgData;
-
-    private FileInputStream mFileIn;
-    private FileOutputStream mFileOut;
-    private FileOutputStream mFileErr;
-
-    private PrintWriter mOutPrintWriter;
-    private PrintWriter mErrPrintWriter;
-    private InputStream mInputStream;
-
-    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, int firstArgPos) {
-        mTarget = target;
-        mIn = in;
-        mOut = out;
-        mErr = err;
-        mArgs = args;
-        mCmd = null;
-        mArgPos = firstArgPos;
-        mCurArgData = null;
-        mFileIn = null;
-        mFileOut = null;
-        mFileErr = null;
-        mOutPrintWriter = null;
-        mErrPrintWriter = null;
-        mInputStream = null;
-    }
-
-    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args) {
-        String cmd;
-        int start;
-        if (args != null && args.length > 0) {
-            cmd = args[0];
-            start = 1;
-        } else {
-            cmd = null;
-            start = 0;
-        }
-        init(target, in, out, err, args, start);
-        mCmd = cmd;
-
-        if (DEBUG) {
-            RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
-            Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
-            Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
-                    + " pid=" + Binder.getCallingPid());
-        }
-        int res = -1;
-        try {
-            res = onCommand(mCmd);
-            if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
-        } catch (Throwable e) {
-            // Unlike usual calls, in this case if an exception gets thrown
-            // back to us we want to print it back in to the dump data, since
-            // that is where the caller expects all interesting information to
-            // go.
-            PrintWriter eout = getErrPrintWriter();
-            eout.println();
-            eout.println("Exception occurred while executing '" + mCmd + "':");
-            e.printStackTrace(eout);
-        } finally {
-            if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
-            if (mOutPrintWriter != null) {
-                mOutPrintWriter.flush();
-            }
-            if (mErrPrintWriter != null) {
-                mErrPrintWriter.flush();
-            }
-            if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
-        }
-        if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
-        return res;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the output stream.
-     */
-    public FileDescriptor getOutFileDescriptor() {
-        return mOut;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's output data stream.
-     */
-    public OutputStream getRawOutputStream() {
-        if (mFileOut == null) {
-            mFileOut = new FileOutputStream(mOut);
-        }
-        return mFileOut;
-    }
-
-    /**
-     * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
-     */
-    public PrintWriter getOutPrintWriter() {
-        if (mOutPrintWriter == null) {
-            mOutPrintWriter = new PrintWriter(getRawOutputStream());
-        }
-        return mOutPrintWriter;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the error stream.
-     */
-    public FileDescriptor getErrFileDescriptor() {
-        return mErr;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's error output data stream.
-     */
-    public OutputStream getRawErrorStream() {
-        if (mFileErr == null) {
-            mFileErr = new FileOutputStream(mErr);
-        }
-        return mFileErr;
-    }
-
-    /**
-     * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
-     */
-    public PrintWriter getErrPrintWriter() {
-        if (mErr == null) {
-            return getOutPrintWriter();
-        }
-        if (mErrPrintWriter == null) {
-            mErrPrintWriter = new PrintWriter(getRawErrorStream());
-        }
-        return mErrPrintWriter;
-    }
-
-    /**
-     * Return the raw FileDescriptor for the input stream.
-     */
-    public FileDescriptor getInFileDescriptor() {
-        return mIn;
-    }
-
-    /**
-     * Return direct raw access (not buffered) to the command's input data stream.
-     */
-    public InputStream getRawInputStream() {
-        if (mFileIn == null) {
-            mFileIn = new FileInputStream(mIn);
-        }
-        return mFileIn;
-    }
-
-    /**
-     * Return buffered access to the command's {@link #getRawInputStream()}.
-     */
-    public InputStream getBufferedInputStream() {
-        if (mInputStream == null) {
-            mInputStream = new BufferedInputStream(getRawInputStream());
-        }
-        return mInputStream;
-    }
-
-    /**
-     * Return the next option on the command line -- that is an argument that
-     * starts with '-'.  If the next argument is not an option, null is returned.
-     */
-    public String getNextOption() {
-        if (mCurArgData != null) {
-            String prev = mArgs[mArgPos - 1];
-            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
-        }
-        if (mArgPos >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mArgPos];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mArgPos++;
-        if (arg.equals("--")) {
-            return null;
-        }
-        if (arg.length() > 1 && arg.charAt(1) != '-') {
-            if (arg.length() > 2) {
-                mCurArgData = arg.substring(2);
-                return arg.substring(0, 2);
-            } else {
-                mCurArgData = null;
-                return arg;
-            }
-        }
-        mCurArgData = null;
-        return arg;
-    }
-
-    /**
-     * Return the next argument on the command line, whatever it is; if there are
-     * no arguments left, return null.
-     */
-    public String getNextArg() {
-        if (mCurArgData != null) {
-            String arg = mCurArgData;
-            mCurArgData = null;
-            return arg;
-        } else if (mArgPos < mArgs.length) {
-            return mArgs[mArgPos++];
-        } else {
-            return null;
-        }
-    }
-
-    public String peekNextArg() {
-        if (mCurArgData != null) {
-            return mCurArgData;
-        } else if (mArgPos < mArgs.length) {
-            return mArgs[mArgPos];
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * @return all the remaining arguments in the command without moving the current position.
-     */
-    public String[] peekRemainingArgs() {
-        int remaining = getRemainingArgsCount();
-        String[] args = new String[remaining];
-        for (int pos = mArgPos; pos < mArgs.length; pos++) {
-            args[pos - mArgPos] = mArgs[pos];
-        }
-        return args;
-    }
-
-    /**
-     * Returns number of arguments that haven't been processed yet.
-     */
-    public int getRemainingArgsCount() {
-        if (mArgPos >= mArgs.length) {
-            return 0;
-        }
-        return mArgs.length - mArgPos;
-    }
-
-    /**
-     * Return the next argument on the command line, whatever it is; if there are
-     * no arguments left, throws an IllegalArgumentException to report this to the user.
-     */
-    public String getNextArgRequired() {
-        String arg = getNextArg();
-        if (arg == null) {
-            String prev = mArgs[mArgPos - 1];
-            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
-        }
-        return arg;
-    }
-
-    public int handleDefaultCommands(String cmd) {
-        if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
-            onHelp();
-        } else {
-            getOutPrintWriter().println("Unknown command: " + cmd);
-        }
-        return -1;
-    }
-
-    public Binder getTarget() {
-        return mTarget;
-    }
-
-    public String[] getAllArgs() {
-        return mArgs;
-    }
-
-    /**
-     * Implement parsing and execution of a command.  If it isn't a command you understand,
-     * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
-     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
-     * to process additional command line arguments.  Command output can be written to
-     * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
-     *
-     * <p class="caution">Note that no permission checking has been done before entering this
-     * function, so you need to be sure to do your own security verification for any commands you
-     * are executing.  The easiest way to do this is to have the ShellCommand contain
-     * only a reference to your service's aidl interface, and do all of your command
-     * implementations on top of that -- that way you can rely entirely on your executing security
-     * code behind that interface.</p>
-     *
-     * @param cmd The first command line argument representing the name of the command to execute.
-     * @return Return the command result; generally 0 or positive indicates success and
-     * negative values indicate error.
-     */
-    public abstract int onCommand(String cmd);
-
-    /**
-     * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
-     */
-    public abstract void onHelp();
-}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 3358ce1..a2173a6 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,15 +19,9 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Slog;
 
-import com.android.internal.util.FastPrintWriter;
+import com.android.modules.utils.BasicShellCommandHandler;
 
-import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
 
 /**
  * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index c89adad..086180e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -85,8 +85,6 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -1045,22 +1043,22 @@
             /**
              * Detect attempts to invoke a method on a {@link Context} that is not suited for such
              * operation.
-             * <p>An example of this is trying to obtain an instance of visual service (e.g.
+             * <p>An example of this is trying to obtain an instance of UI service (e.g.
              * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
              * allowed, since a non-visual {@link Context} is not adjusted to any visual area, and
              * therefore can report incorrect metrics or resources.
              * @see Context#getDisplay()
              * @see Context#getSystemService(String)
-             * @hide
              */
-            @TestApi
             public @NonNull Builder detectIncorrectContextUse() {
                 return enable(DETECT_VM_INCORRECT_CONTEXT_USE);
             }
 
             /**
              * Disable detection of incorrect context use.
-             * TODO(b/149790106): Fix usages and remove.
+             *
+             * @see #detectIncorrectContextUse()
+             *
              * @hide
              */
             @TestApi
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 97c9f4b..714bcea 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -457,6 +457,20 @@
      */
     public static final String NAMESPACE_CONFIGURATION = "configuration";
 
+    /**
+     * LatencyTracker properties definitions.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
+
+    /**
+     * InteractionJankMonitor properties definitions.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
index 8b7d6ad..97e0156 100644
--- a/core/java/android/provider/OWNERS
+++ b/core/java/android/provider/OWNERS
@@ -1,4 +1,5 @@
 per-file DeviceConfig.java = svetoslavganov@google.com
 per-file DeviceConfig.java = hackbod@google.com
+per-file DeviceConfig.java = schfan@google.com
 
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a50a835..884f8cc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14584,19 +14584,6 @@
          */
         public static final String MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH =
                 "maximum_obscuring_opacity_for_touch";
-
-        /**
-         * LatencyTracker settings.
-         *
-         * The following strings are supported as keys:
-         * <pre>
-         *     enabled              (boolean)
-         *     sampling_interval    (int)
-         * </pre>
-         *
-         * @hide
-         */
-        public static final String LATENCY_TRACKER = "latency_tracker";
     }
 
     /**
diff --git a/core/java/android/service/attestation/IImpressionAttestationService.aidl b/core/java/android/service/attestation/IImpressionAttestationService.aidl
index 8e858b8..fcbc51f 100644
--- a/core/java/android/service/attestation/IImpressionAttestationService.aidl
+++ b/core/java/android/service/attestation/IImpressionAttestationService.aidl
@@ -18,8 +18,8 @@
 
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
-import android.service.attestation.ImpressionToken;
 import android.os.RemoteCallback;
+import android.service.attestation.ImpressionToken;
 
 /**
  * Service used to handle impression attestation requests.
@@ -31,22 +31,26 @@
      * Generates the impression token that can be used to validate that the system generated the
      * token.
      *
-     * @param screenshot The token for the window where the view is shown.
+     * @param salt The salt to use when generating the hmac. This should be unique to the caller so
+     *        the token cannot be verified by any other process.
+     * @param screenshot The screenshot to generate the hash and add to the token.
      * @param bounds The size and position of the content being attested in the window.
      * @param hashAlgorithm The String for the hashing algorithm to use based on values in
      *        {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS}.
      * @param Callback The callback invoked to send back the impression token.
      */
-    void generateImpressionToken(in HardwareBuffer screenshot, in Rect bounds,
+    void generateImpressionToken(in String salt, in HardwareBuffer screenshot, in Rect bounds,
                                  in String hashAlgorithm, in RemoteCallback callback);
 
     /**
      * Call to verify that the impressionToken passed in was generated by the system. The result
-     * will be sent in the callback as an integer with the key {@link #EXTRA_VERIFICATION_STATUS}
-     * and will be one of the values in {@link VerificationStatus}.
+     * will be sent in the callback as a boolean with the key {@link #EXTRA_VERIFICATION_STATUS}.
      *
+     * @param salt The salt value to use when verifying the hmac. This should be the same value that
+     *        was passed to {@link generateImpressionToken()} to generate the token.
      * @param impressionToken The token to verify that it was generated by the system.
      * @param callback The callback invoked to send back the verification status.
      */
-    void verifyImpressionToken(in ImpressionToken impressionToken, in RemoteCallback callback);
+    void verifyImpressionToken(in String salt, in ImpressionToken impressionToken,
+                               in RemoteCallback callback);
 }
diff --git a/core/java/android/service/attestation/ImpressionAttestationService.java b/core/java/android/service/attestation/ImpressionAttestationService.java
index 4919f5d..05ad5f0 100644
--- a/core/java/android/service/attestation/ImpressionAttestationService.java
+++ b/core/java/android/service/attestation/ImpressionAttestationService.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -50,27 +49,24 @@
     public static final String EXTRA_VERIFICATION_STATUS =
             "android.service.attestation.extra.VERIFICATION_STATUS";
 
-    /** @hide */
-    @IntDef(prefix = {"VERIFICATION_STATUS_"}, value = {
-            VERIFICATION_STATUS_UNKNOWN,
-            VERIFICATION_STATUS_OS_VERIFIED,
-            VERIFICATION_STATUS_APP_DECLARED
-    })
-    public @interface VerificationStatus {
-    }
-
-    public static final int VERIFICATION_STATUS_UNKNOWN = 0;
-    public static final int VERIFICATION_STATUS_OS_VERIFIED = 1;
-    public static final int VERIFICATION_STATUS_APP_DECLARED = 2;
-
     /**
      * Manifest metadata key for the resource string array containing the names of all impression
      * attestation algorithms provided by the service.
+     *
      * @hide
      */
     public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
             "android.attestation.available_algorithms";
 
+    /**
+     * The {@link Intent} action that must be declared as handled by a service in its manifest
+     * for the system to recognize it as an impression attestation providing service.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.attestation.ImpressionAttestationService";
+
     private ImpressionAttestationServiceWrapper mWrapper;
     private Handler mHandler;
 
@@ -94,6 +90,8 @@
      * Generates the impression token that can be used to validate that the system
      * generated the token.
      *
+     * @param salt          The salt to use when generating the hmac. This should be unique to the
+     *                      caller so the token cannot be verified by any other process.
      * @param screenshot    The screenshot buffer for the content to attest.
      * @param bounds        The size and position of the content being attested in the window.
      * @param hashAlgorithm The String for the hashing algorithm to use based values in
@@ -102,51 +100,57 @@
      * Returns null when the arguments sent are invalid.
      */
     @Nullable
-    public abstract ImpressionToken onGenerateImpressionToken(@NonNull HardwareBuffer screenshot,
-            @NonNull Rect bounds, @NonNull String hashAlgorithm);
+    public abstract ImpressionToken onGenerateImpressionToken(@NonNull String salt,
+            @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
+            @NonNull String hashAlgorithm);
 
     /**
      * Call to verify that the impressionToken passed in was generated by the system.
      *
+     * @param salt            The salt value to use when verifying the hmac. This should be the
+     *                        same value that was passed to
+     *                        {@link #onGenerateImpressionToken(String,
+     *                        HardwareBuffer, Rect, String)} to
+     *                        generate the token.
      * @param impressionToken The token to verify that it was generated by the system.
-     * @return A {@link VerificationStatus} about whether the token was generated by the system.
+     * @return true if the token can be verified that it was generated by the system.
      */
-    public abstract @VerificationStatus int onVerifyImpressionToken(
+    public abstract boolean onVerifyImpressionToken(@NonNull String salt,
             @NonNull ImpressionToken impressionToken);
 
-    private void generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+    private void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds,
             String hashAlgorithm, RemoteCallback callback) {
-        ImpressionToken impressionToken = onGenerateImpressionToken(screenshot, bounds,
+        ImpressionToken impressionToken = onGenerateImpressionToken(salt, screenshot, bounds,
                 hashAlgorithm);
         final Bundle data = new Bundle();
         data.putParcelable(EXTRA_IMPRESSION_TOKEN, impressionToken);
         callback.sendResult(data);
     }
 
-    private void verifyImpressionToken(ImpressionToken impressionToken,
+    private void verifyImpressionToken(String salt, ImpressionToken impressionToken,
             RemoteCallback callback) {
-        @VerificationStatus int verificationStatus = onVerifyImpressionToken(impressionToken);
+        boolean verificationStatus = onVerifyImpressionToken(salt, impressionToken);
         final Bundle data = new Bundle();
-        data.putInt(EXTRA_VERIFICATION_STATUS, verificationStatus);
+        data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
         callback.sendResult(data);
     }
 
     private final class ImpressionAttestationServiceWrapper extends
             IImpressionAttestationService.Stub {
         @Override
-        public void generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+        public void generateImpressionToken(String salt, HardwareBuffer screenshot, Rect bounds,
                 String hashAlgorithm, RemoteCallback callback) {
             mHandler.sendMessage(
                     obtainMessage(ImpressionAttestationService::generateImpressionToken,
-                            ImpressionAttestationService.this, screenshot, bounds, hashAlgorithm,
-                            callback));
+                            ImpressionAttestationService.this, salt, screenshot, bounds,
+                            hashAlgorithm, callback));
         }
 
         @Override
-        public void verifyImpressionToken(ImpressionToken impressionToken,
+        public void verifyImpressionToken(String salt, ImpressionToken impressionToken,
                 RemoteCallback callback) {
             mHandler.sendMessage(obtainMessage(ImpressionAttestationService::verifyImpressionToken,
-                    ImpressionAttestationService.this, impressionToken, callback));
+                    ImpressionAttestationService.this, salt, impressionToken, callback));
         }
     }
 }
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index b4b5819..53290e2 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -176,6 +176,9 @@
 
     @Override
     public void onDestroy() {
+        // As this call will come asynchronously in the main thread, prevent calls from the binder
+        // being processed after this.
+        mHandler.removeCallbacksAndMessages(null);
         if (mListening) {
             onStopListening();
             mListening = false;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bfa3123..479a0c1 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -17,10 +17,7 @@
 package android.telephony;
 
 import android.Manifest;
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.compat.annotation.ChangeId;
@@ -31,27 +28,17 @@
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.telephony.Annotation.CallState;
-import android.telephony.Annotation.DataActivityType;
-import android.telephony.Annotation.DisconnectCauses;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
-import android.telephony.NetworkRegistrationInfo.Domain;
-import android.telephony.TelephonyManager.DataEnabledReason;
-import android.telephony.TelephonyManager.DataState;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
 
 import dalvik.system.VMRuntime;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.Map;
@@ -126,9 +113,7 @@
      *
      *  @see #onServiceStateChanged
      *  @see ServiceState
-     *  @deprecated Use {@link ServiceStateChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_SERVICE_STATE                            = 0x00000001;
 
     /**
@@ -136,7 +121,8 @@
      * {@more}
      *
      * @see #onSignalStrengthChanged
-     * @deprecated Use {@link SignalStrengthsChangedListener} instead.
+     *
+     * @deprecated by {@link #LISTEN_SIGNAL_STRENGTHS}
      */
     @Deprecated
     public static final int LISTEN_SIGNAL_STRENGTH                          = 0x00000002;
@@ -152,9 +138,7 @@
      * voicemail icon.
      *
      * @see #onMessageWaitingIndicatorChanged
-     * @deprecated Use {@link MessageWaitingIndicatorChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_MESSAGE_WAITING_INDICATOR                = 0x00000004;
 
     /**
@@ -165,9 +149,7 @@
      * {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onCallForwardingIndicatorChanged
-     * @deprecated Use {@link CallForwardingIndicatorChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_CALL_FORWARDING_INDICATOR                = 0x00000008;
 
     /**
@@ -183,9 +165,7 @@
      * instead.
      *
      * @see #onCellLocationChanged
-     * @deprecated Use {@link CellLocationChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_CELL_LOCATION                            = 0x00000010;
 
     /**
@@ -193,18 +173,14 @@
      * {@more}
      *
      * @see #onCallStateChanged
-     * @deprecated Use {@link CallStateChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_CALL_STATE                               = 0x00000020;
 
     /**
      * Listen for changes to the data connection state (cellular).
      *
      * @see #onDataConnectionStateChanged
-     * @deprecated Use {@link DataConnectionStateChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_DATA_CONNECTION_STATE                    = 0x00000040;
 
     /**
@@ -215,9 +191,7 @@
      * data-traffic icon.
      *
      * @see #onDataActivity
-     * @deprecated Use {@link DataActivityListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_DATA_ACTIVITY                            = 0x00000080;
 
     /**
@@ -227,9 +201,7 @@
      * icon.
      *
      * @see #onSignalStrengthsChanged
-     * @deprecated Use {@link SignalStrengthsChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_SIGNAL_STRENGTHS                         = 0x00000100;
 
     /**
@@ -239,9 +211,7 @@
      * @see #onSignalStrengthsChanged
      *
      * @hide
-     * @deprecated Use {@link AlwaysReportedSignalStrengthsChangedListener} instead.
      */
-    @Deprecated
     @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
     public static final int LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH          = 0x00000200;
 
@@ -252,9 +222,7 @@
      * permission.
      *
      * @see #onCellInfoChanged
-     * @deprecated Use {@link CellInfoChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_CELL_INFO = 0x00000400;
 
     /**
@@ -266,10 +234,8 @@
      * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @hide
-     * @deprecated Use {@link PreciseCallStateChangedListener} instead.
      */
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     @SystemApi
     public static final int LISTEN_PRECISE_CALL_STATE                       = 0x00000800;
 
@@ -281,10 +247,8 @@
      * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onPreciseDataConnectionStateChanged
-     * @deprecated Use {@link PreciseDataConnectionStateChangedListener} instead.
      */
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE            = 0x00001000;
 
     /**
@@ -294,7 +258,7 @@
      * READ_PRECISE_PHONE_STATE}
      * @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo)
      *
-     * @deprecated Use {@link TelephonyManager#requestModemActivityInfo} instead.
+     * @deprecated Use {@link TelephonyManager#getModemActivityInfo()}
      * @hide
      */
     @Deprecated
@@ -307,9 +271,7 @@
      *
      * @see #onServiceStateChanged(ServiceState)
      * @hide
-     * @deprecated Use {@link SrvccStateChangedListener} instead.
      */
-    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public static final int LISTEN_SRVCC_STATE_CHANGED                     = 0x00004000;
@@ -327,11 +289,10 @@
     /**
      * Listen for carrier network changes indicated by a carrier app.
      *
-     * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)
+     * @see #onCarrierNetworkRequest
+     * @see TelephonyManager#notifyCarrierNetworkChange(boolean)
      * @hide
-     * @deprecated Use {@link CarrierNetworkChangeListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_CARRIER_NETWORK_CHANGE                   = 0x00010000;
 
     /**
@@ -350,9 +311,7 @@
      *
      * @see #onVoiceActivationStateChanged
      * @hide
-     * @deprecated Use {@link VoiceActivationStateChangedListener} instead.
      */
-    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public static final int LISTEN_VOICE_ACTIVATION_STATE                   = 0x00020000;
@@ -364,24 +323,20 @@
      * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
      * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
      * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
-     *
+     * {@more}
      * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
      * fully activated
      *
      * @see #onDataActivationStateChanged
      * @hide
-     * @deprecated Use {@link DataActivationStateChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_DATA_ACTIVATION_STATE                   = 0x00040000;
 
     /**
      *  Listen for changes to the user mobile data state
      *
      *  @see #onUserMobileDataStateChanged
-     *  @deprecated Use {@link UserMobileDataStateChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_USER_MOBILE_DATA_STATE                  = 0x00080000;
 
     /**
@@ -392,9 +347,7 @@
      *  {@link TelephonyManager#hasCarrierPrivileges}).
      *
      *  @see #onDisplayInfoChanged
-     * @deprecated Use {@link DisplayInfoChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
 
     /**
@@ -402,9 +355,7 @@
      *
      *  @see #onPhoneCapabilityChanged
      *  @hide
-     *  @deprecated Use {@link PhoneCapabilityChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_PHONE_CAPABILITY_CHANGE                 = 0x00200000;
 
     /**
@@ -414,19 +365,17 @@
      *  subscription user selected as default data subscription in DSDS mode.
      *
      *  @see #onActiveDataSubscriptionIdChanged
-     *  @deprecated Use {@link ActiveDataSubscriptionIdChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000;
 
     /**
      *  Listen for changes to the radio power state.
      *
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+     *
      *  @see #onRadioPowerStateChanged
      *  @hide
-     *  @deprecated Use {@link RadioPowerStateChangedListener} instead.
      */
-    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public static final int LISTEN_RADIO_POWER_STATE_CHANGED               = 0x00800000;
@@ -436,10 +385,7 @@
      *
      * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
      * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @deprecated Use {@link EmergencyNumberListChangedListener} instead.
      */
-    @Deprecated
     public static final int LISTEN_EMERGENCY_NUMBER_LIST                   = 0x01000000;
 
     /**
@@ -450,10 +396,8 @@
      * or the calling app has carrier privileges
      * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @deprecated Use {@link CallDisconnectCauseChangedListener} instead.
      */
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_CALL_DISCONNECT_CAUSES                  = 0x02000000;
 
     /**
@@ -465,11 +409,9 @@
      *
      * @see #onCallAttributesChanged
      * @hide
-     * @deprecated Use {@link CallAttributesChangedListener} instead.
      */
-    @Deprecated
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_CALL_ATTRIBUTES_CHANGED                 = 0x04000000;
 
     /**
@@ -481,20 +423,18 @@
      * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onImsCallDisconnectCauseChanged(ImsReasonInfo)
-     * @deprecated Use {@link ImsCallDisconnectCauseChangedListener} instead.
      */
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES              = 0x08000000;
 
     /**
      * Listen for the emergency number placed from an outgoing call.
      *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
      * @see #onOutgoingEmergencyCall
      * @hide
-     * @deprecated Use {@link OutgoingEmergencyCallListener} instead.
      */
-    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
     public static final int LISTEN_OUTGOING_EMERGENCY_CALL                  = 0x10000000;
@@ -502,11 +442,11 @@
     /**
      * Listen for the emergency number placed from an outgoing SMS.
      *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
      * @see #onOutgoingEmergencySms
      * @hide
-     * @deprecated Use {@link OutgoingEmergencySmsListener} instead.
      */
-    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
     public static final int LISTEN_OUTGOING_EMERGENCY_SMS                   = 0x20000000;
@@ -525,9 +465,7 @@
      * of whether the calling app has carrier privileges.
      *
      * @see #onRegistrationFailed
-     * @deprecated Use {@link RegistrationFailedListener} instead.
      */
-    @Deprecated
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
 
@@ -541,525 +479,19 @@
      * of whether the calling app has carrier privileges.
      *
      * @see #onBarringInfoChanged
-     * @deprecated Use {@link BarringInfoChangedListener} instead.
      */
-    @Deprecated
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public static final int LISTEN_BARRING_INFO = 0x80000000;
 
     /**
-     *  Event for changes to the network service state (cellular).
+     *  Listen for changes to the physical channel configuration.
      *
-     *  @see ServiceStateChangedListener#onServiceStateChanged
-     *  @see ServiceState
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_SERVICE_STATE_CHANGED = 1;
-
-    /**
-     * Event for changes to the network signal strength (cellular).
-     *
-     * @see SignalStrengthsChangedListener#onSignalStrengthsChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2;
-
-    /**
-     * Event for changes to the message-waiting indicator.
-     *
-     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that
-     * the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}).
-     * <p>
-     * Example: The status bar uses this to determine when to display the
-     * voicemail icon.
-     *
-     * @see MessageWaitingIndicatorChangedListener#onMessageWaitingIndicatorChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3;
-
-    /**
-     * Event for changes to the call-forwarding indicator.
-     *
-     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that
-     * the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see CallForwardingIndicatorChangedListener#onCallForwardingIndicatorChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4;
-
-    /**
-     * Event for changes to the device's cell location. Note that
-     * this will result in frequent callbacks to the listener.
-     *
-     * If you need regular location updates but want more control over
-     * the update interval or location precision, you can set up a listener
-     * through the {@link android.location.LocationManager location manager}
-     * instead.
-     *
-     * @see CellLocationChangedListener#onCellLocationChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
-    public static final int EVENT_CELL_LOCATION_CHANGED = 5;
-
-    /**
-     * Event for changes to the device call state.
-     *
-     * @see CallStateChangedListener#onCallStateChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_CALL_LOG)
-    public static final int EVENT_CALL_STATE_CHANGED = 6;
-
-    /**
-     * Event for changes to the data connection state (cellular).
-     *
-     * @see DataConnectionStateChangedListener#onDataConnectionStateChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_DATA_CONNECTION_STATE_CHANGED = 7;
-
-    /**
-     * Event for changes to the direction of data traffic on the data
-     * connection (cellular).
-     *
-     * Example: The status bar uses this to display the appropriate
-     * data-traffic icon.
-     *
-     * @see DataActivityListener#onDataActivity
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_DATA_ACTIVITY_CHANGED = 8;
-
-    /**
-     * Event for changes to the network signal strengths (cellular).
-     * <p>
-     * Example: The status bar uses this to control the signal-strength
-     * icon.
-     *
-     * @see SignalStrengthsChangedListener#onSignalStrengthsChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9;
-
-    /**
-     * Event for changes of the network signal strengths (cellular) always reported from modem,
-     * even in some situations such as the screen of the device is off.
-     *
-     * @see AlwaysReportedSignalStrengthsChangedListener#onSignalStrengthsChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
-    public static final int EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED = 10;
-
-    /**
-     * Event for changes to observed cell info.
-     *
-     * @see CellInfoChangedListener#onCellInfoChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
-    public static final int EVENT_CELL_INFO_CHANGED = 11;
-
-    /**
-     * Event for {@link android.telephony.Annotation.PreciseCallStates} of ringing,
-     * background and foreground calls.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see PreciseCallStateChangedListener#onPreciseCallStateChanged
-     *
+     * @see #onPhysicalChannelConfigurationChanged
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_PRECISE_CALL_STATE_CHANGED = 12;
-
-    /**
-     * Event for {@link PreciseDataConnectionState} on the data connection (cellular).
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see PreciseDataConnectionStateChangedListener#onPreciseDataConnectionStateChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13;
-
-    /**
-     * Event for real time info for all data connections (cellular)).
-     *
-     * @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo)
-     *
-     * @deprecated Use {@link TelephonyManager#requestModemActivityInfo}
-     * @hide
-     */
-    @Deprecated
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED = 14;
-
-    /**
-     * Event for OEM hook raw event
-     *
-     * @see #onOemHookRawEvent
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public static final int EVENT_OEM_HOOK_RAW = 15;
-
-    /**
-     * Event for changes to the SRVCC state of the active call.
-     *
-     * @see SrvccStateChangedListener#onSrvccStateChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public static final int EVENT_SRVCC_STATE_CHANGED = 16;
-
-    /**
-     * Event for carrier network changes indicated by a carrier app.
-     *
-     * @see android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)
-     * @see CarrierNetworkChangeListener#onCarrierNetworkChange
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_CARRIER_NETWORK_CHANGED = 17;
-
-    /**
-     * Event for changes to the sim voice activation state
-     *
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
-     *
-     * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
-     * fully activated
-     *
-     * @see VoiceActivationStateChangedListener#onVoiceActivationStateChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18;
-
-    /**
-     * Event for changes to the sim data activation state
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
-     * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
-     *
-     * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
-     * fully activated
-     *
-     * @see DataActivationStateChangedListener#onDataActivationStateChanged
-     * @hide
-     */
-    @SystemApi
-    public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19;
-
-    /**
-     *  Event for changes to the user mobile data state
-     *
-     *  @see UserMobileDataStateChangedListener#onUserMobileDataStateChanged
-     *
-     *  @hide
-     */
-    @SystemApi
-    public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20;
-
-    /**
-     *  Event for display info changed event.
-     *
-     *  @see DisplayInfoChangedListener#onDisplayInfoChanged
-     *
-     *  @hide
-     */
-    @SystemApi
-    public static final int EVENT_DISPLAY_INFO_CHANGED = 21;
-
-    /**
-     *  Event for changes to the phone capability.
-     *
-     *  @see PhoneCapabilityChangedListener#onPhoneCapabilityChanged
-     *
-     *  @hide
-     */
-    @SystemApi
-    public static final int EVENT_PHONE_CAPABILITY_CHANGED = 22;
-
-    /**
-     *  Event for changes to active data subscription ID. Active data subscription is
-     *  the current subscription used to setup Cellular Internet data. For example,
-     *  it could be the current active opportunistic subscription in use, or the
-     *  subscription user selected as default data subscription in DSDS mode.
-     *
-     *  <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
-     *  app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     *  @see ActiveDataSubscriptionIdChangedListener#onActiveDataSubscriptionIdChanged
-     *
-     *  @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public static final int EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED = 23;
-
-    /**
-     *  Event for changes to the radio power state.
-     *
-     *  @see RadioPowerStateChangedListener#onRadioPowerStateChanged
-     *
-     *  @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24;
-
-    /**
-     * Event for changes to emergency number list based on all active subscriptions.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
-     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     *  @see EmergencyNumberListChangedListener#onEmergencyNumberListChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25;
-
-    /**
-     * Event for call disconnect causes which contains {@link DisconnectCause} and
-     * {@link PreciseDisconnectCause}.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     *  @see CallDisconnectCauseChangedListener#onCallDisconnectCauseChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_CALL_DISCONNECT_CAUSE_CHANGED = 26;
-
-    /**
-     * Event for changes to the call attributes of a currently active call.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see CallAttributesChangedListener#onCallAttributesChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_CALL_ATTRIBUTES_CHANGED = 27;
-
-    /**
-     * Event for IMS call disconnect causes which contains
-     * {@link android.telephony.ims.ImsReasonInfo}
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see ImsCallDisconnectCauseChangedListener#onImsCallDisconnectCauseChanged(ImsReasonInfo)
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28;
-
-    /**
-     * Event for the emergency number placed from an outgoing call.
-     *
-     * @see OutgoingEmergencyCallListener#onOutgoingEmergencyCall
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
-    public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29;
-
-    /**
-     * Event for the emergency number placed from an outgoing SMS.
-     *
-     * @see OutgoingEmergencySmsListener#onOutgoingEmergencySms
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
-    public static final int EVENT_OUTGOING_EMERGENCY_SMS = 30;
-
-    /**
-     * Event for registration failures.
-     *
-     * Event for indications that a registration procedure has failed in either the CS or PS
-     * domain. This indication does not necessarily indicate a change of service state, which should
-     * be tracked via {@link #EVENT_SERVICE_STATE_CHANGED}.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
-     * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
-     *
-     * @see RegistrationFailedListener#onRegistrationFailed
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(allOf = {
-            Manifest.permission.READ_PRECISE_PHONE_STATE,
-            Manifest.permission.ACCESS_FINE_LOCATION
-    })
-    public static final int EVENT_REGISTRATION_FAILURE = 31;
-
-    /**
-     * Event for Barring Information for the current registered / camped cell.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
-     * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
-     *
-     * @see BarringInfoChangedListener#onBarringInfoChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(allOf = {
-            Manifest.permission.READ_PRECISE_PHONE_STATE,
-            Manifest.permission.ACCESS_FINE_LOCATION
-    })
-    public static final int EVENT_BARRING_INFO_CHANGED = 32;
-
-    /**
-     * Event for changes to the physical channel configuration.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see PhysicalChannelConfigChangedListener#onPhysicalChannelConfigChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 33;
-
-
-    /**
-     * Event for changes to the data enabled.
-     *
-     * Event for indications that the enabled status of current data has changed.
-     *
-     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-     * or the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
-     * @see DataEnabledChangedListener#onDataEnabledChanged
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public static final int EVENT_DATA_ENABLED_CHANGED = 34;
-
-    /** @hide */
-    @IntDef(prefix = { "EVENT_" }, value = {
-            EVENT_SERVICE_STATE_CHANGED,
-            EVENT_SIGNAL_STRENGTH_CHANGED,
-            EVENT_MESSAGE_WAITING_INDICATOR_CHANGED,
-            EVENT_CALL_FORWARDING_INDICATOR_CHANGED,
-            EVENT_CELL_LOCATION_CHANGED,
-            EVENT_CALL_STATE_CHANGED,
-            EVENT_DATA_CONNECTION_STATE_CHANGED,
-            EVENT_DATA_ACTIVITY_CHANGED,
-            EVENT_SIGNAL_STRENGTHS_CHANGED,
-            EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED,
-            EVENT_CELL_INFO_CHANGED,
-            EVENT_PRECISE_CALL_STATE_CHANGED,
-            EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED,
-            EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED,
-            EVENT_OEM_HOOK_RAW,
-            EVENT_SRVCC_STATE_CHANGED,
-            EVENT_CARRIER_NETWORK_CHANGED,
-            EVENT_VOICE_ACTIVATION_STATE_CHANGED,
-            EVENT_DATA_ACTIVATION_STATE_CHANGED,
-            EVENT_USER_MOBILE_DATA_STATE_CHANGED,
-            EVENT_DISPLAY_INFO_CHANGED,
-            EVENT_PHONE_CAPABILITY_CHANGED,
-            EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED,
-            EVENT_RADIO_POWER_STATE_CHANGED,
-            EVENT_EMERGENCY_NUMBER_LIST_CHANGED,
-            EVENT_CALL_DISCONNECT_CAUSE_CHANGED,
-            EVENT_CALL_ATTRIBUTES_CHANGED,
-            EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED,
-            EVENT_OUTGOING_EMERGENCY_CALL,
-            EVENT_OUTGOING_EMERGENCY_SMS,
-            EVENT_REGISTRATION_FAILURE,
-            EVENT_BARRING_INFO_CHANGED,
-            EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED,
-            EVENT_DATA_ENABLED_CHANGED
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TelephonyEvent {}
+    public static final long LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 0x100000000L;
 
     /*
      * Subscription used to listen to the phone state changes
@@ -1072,13 +504,9 @@
     /**
      * @hide
      */
-    //TODO: The maxTargetSdk should be S if the build time tool updates it.
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    @UnsupportedAppUsage(
-            maxTargetSdk = Build.VERSION_CODES.R,
-            publicAlternatives = "Use {@code TelephonyManager#registerPhoneStateListener(" +
-                    "Executor, PhoneStateListener)} instead")
-    public IPhoneStateListener callback;
+    @UnsupportedAppUsage
+    public final IPhoneStateListener callback;
 
     /**
      * Create a PhoneStateListener for the Phone with the default subscription.
@@ -1135,737 +563,17 @@
      * The Executor must not be null.
      *
      * @param executor a non-null Executor that will execute callbacks for the PhoneStateListener.
-     * @deprecated Use
-     * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)} instead.
      */
-    @Deprecated
     public PhoneStateListener(@NonNull Executor executor) {
         this(null, executor);
     }
 
-    /**
-     * @hide
-     */
-    public void setExecutor(@NonNull @CallbackExecutor Executor executor) {
-        if (executor == null) {
+    private PhoneStateListener(Integer subId, Executor e) {
+        if (e == null) {
             throw new IllegalArgumentException("PhoneStateListener Executor must be non-null");
         }
-        callback = new IPhoneStateListenerStub(this, executor);
-    }
-
-    private PhoneStateListener(Integer subId, Executor e) {
-        setExecutor(e);
         mSubId = subId;
-    }
-
-    /**
-     * Interface for service state listener.
-     */
-    public interface ServiceStateChangedListener {
-        /**
-         * Callback invoked when device service state changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * The instance of {@link ServiceState} passed as an argument here will have various
-         * levels of location information stripped from it depending on the location permissions
-         * that your app holds.
-         * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will
-         * receive all the information in {@link ServiceState}.
-         *
-         * @see ServiceState#STATE_EMERGENCY_ONLY
-         * @see ServiceState#STATE_IN_SERVICE
-         * @see ServiceState#STATE_OUT_OF_SERVICE
-         * @see ServiceState#STATE_POWER_OFF
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onServiceStateChanged(@NonNull ServiceState serviceState);
-    }
-
-    /**
-     * Interface for message waiting indicator listener.
-     */
-    public interface MessageWaitingIndicatorChangedListener {
-        /**
-         * Callback invoked when the message-waiting indicator changes on the registered
-         * subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-        public void onMessageWaitingIndicatorChanged(boolean mwi);
-    }
-
-    /**
-     * Interface for call-forwarding indicator listener.
-     */
-    public interface CallForwardingIndicatorChangedListener {
-        /**
-         * Callback invoked when the call-forwarding indicator changes on the registered
-         * subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-        public void onCallForwardingIndicatorChanged(boolean cfi);
-    }
-
-    /**
-     * Interface for device cell location listener.
-     */
-    public interface CellLocationChangedListener {
-        /**
-         * Callback invoked when device cell location changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
-        public void onCellLocationChanged(@NonNull CellLocation location);
-    }
-
-    /**
-     * Interface for call state listener.
-     */
-    public interface CallStateChangedListener {
-        /**
-         * Callback invoked when device call state changes.
-         * <p>
-         * Reports the state of Telephony (mobile) calls on the device for the registered s
-         * ubscription.
-         * <p>
-         * Note: the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         * <p>
-         * Note: The state returned here may differ from that returned by
-         * {@link TelephonyManager#getCallState()}. Receivers of this callback should be aware that
-         * calling {@link TelephonyManager#getCallState()} from within this callback may return a
-         * different state than the callback reports.
-         *
-         * @param state call state
-         * @param phoneNumber call phone number. If application does not have
-         * {@link android.Manifest.permission#READ_CALL_LOG} permission or carrier
-         * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an empty string will be
-         * passed as an argument.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_CALL_LOG)
-        public void onCallStateChanged(@CallState int state, @Nullable String phoneNumber);
-    }
-
-    /**
-     * Interface for data connection state listener.
-     */
-    public interface DataConnectionStateChangedListener {
-        /**
-         * Callback invoked when connection state changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @see TelephonyManager#DATA_DISCONNECTED
-         * @see TelephonyManager#DATA_CONNECTING
-         * @see TelephonyManager#DATA_CONNECTED
-         * @see TelephonyManager#DATA_SUSPENDED
-         *
-         * @param state is the current state of data connection.
-         * @param networkType is the current network type of data connection.
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onDataConnectionStateChanged(@DataState int state,
-                                                 @NetworkType int networkType);
-    }
-
-    /**
-     * Interface for data activity state listener.
-     */
-    public interface DataActivityListener {
-        /**
-         * Callback invoked when data activity state changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @see TelephonyManager#DATA_ACTIVITY_NONE
-         * @see TelephonyManager#DATA_ACTIVITY_IN
-         * @see TelephonyManager#DATA_ACTIVITY_OUT
-         * @see TelephonyManager#DATA_ACTIVITY_INOUT
-         * @see TelephonyManager#DATA_ACTIVITY_DORMANT
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onDataActivity(@DataActivityType int direction);
-    }
-
-    /**
-     * Interface for network signal strengths listener.
-     */
-    public interface SignalStrengthsChangedListener {
-        /**
-         * Callback invoked when network signal strengths changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength);
-    }
-
-    /**
-     * Interface for network signal strengths listener which always reported from modem.
-     */
-    public interface AlwaysReportedSignalStrengthsChangedListener {
-        /**
-         * Callback always invoked from modem when network signal strengths changes on the
-         * registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
-        public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength);
-    }
-
-    /**
-     * Interface for cell info listener.
-     */
-    public interface CellInfoChangedListener {
-        /**
-         * Callback invoked when a observed cell info has changed or new cells have been added
-         * or removed on the registered subscription.
-         * Note, the registration subscription ID s from {@link TelephonyManager} object
-         * which registersPhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param cellInfo is the list of currently visible cells.
-         */
-        @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
-        public void onCellInfoChanged(@NonNull List<CellInfo> cellInfo);
-    }
-
-    /**
-     * Interface for precise device call state listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface PreciseCallStateChangedListener {
-        /**
-         * Callback invoked when precise device call state changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param callState {@link PreciseCallState}
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onPreciseCallStateChanged(@NonNull PreciseCallState callState);
-    }
-
-    /**
-     * Interface for call disconnect cause listener.
-     */
-    public interface CallDisconnectCauseChangedListener {
-        /**
-         * Callback invoked when call disconnect cause changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param disconnectCause {@link DisconnectCause}.
-         * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause,
-                @PreciseDisconnectCauses int preciseDisconnectCause);
-    }
-
-    /**
-     * Interface for IMS call disconnect cause listener.
-     */
-    public interface ImsCallDisconnectCauseChangedListener {
-        /**
-         * Callback invoked when IMS call disconnect cause changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
-         *
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo);
-    }
-
-    /**
-     * Interface for precise data connection state listener.
-     */
-    public interface PreciseDataConnectionStateChangedListener {
-        /**
-         * Callback providing update about the default/internet data connection on the registered
-         * subscription.
-         *
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-         * or the calling app has carrier privileges
-         * (see {@link TelephonyManager#hasCarrierPrivileges}).
-         *
-         * @param dataConnectionState {@link PreciseDataConnectionState}
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onPreciseDataConnectionStateChanged(
-                @NonNull PreciseDataConnectionState dataConnectionState);
-    }
-
-    /**
-     * Interface for Single Radio Voice Call Continuity listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface SrvccStateChangedListener {
-        /**
-         * Callback invoked when there has been a change in the Single Radio Voice Call Continuity
-         * (SRVCC) state for the currently active call on the registered subscription.
-         *
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-        public void onSrvccStateChanged(@SrvccState int srvccState);
-    }
-
-    /**
-     * Interface for SIM voice activation state listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface VoiceActivationStateChangedListener {
-        /**
-         * Callback invoked when the SIM voice activation state has changed on the registered
-         * subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param state is the current SIM voice activation state
-         */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-        public void onVoiceActivationStateChanged(@SimActivationState int state);
-
-    }
-
-    /**
-     * Interface for SIM data activation state listener.
-     */
-    public interface DataActivationStateChangedListener {
-        /**
-         * Callback invoked when the SIM data activation state has changed on the registered
-         * subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param state is the current SIM data activation state
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onDataActivationStateChanged(@SimActivationState int state);
-    }
-
-    /**
-     * Interface for user mobile data state listener.
-     */
-    public interface UserMobileDataStateChangedListener {
-        /**
-         * Callback invoked when the user mobile data state has changed on the registered
-         * subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param enabled indicates whether the current user mobile data state is enabled or
-         *                disabled.
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onUserMobileDataStateChanged(boolean enabled);
-    }
-
-    /**
-     * Interface for display info listener.
-     */
-    public interface DisplayInfoChangedListener {
-        /**
-         * Callback invoked when the display info has changed on the registered subscription.
-         * <p> The {@link TelephonyDisplayInfo} contains status information shown to the user
-         * based on carrier policy.
-         *
-         * @param telephonyDisplayInfo The display information.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-        public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo);
-    }
-
-    /**
-     * Interface for the current emergency number list listener.
-     */
-    public interface EmergencyNumberListChangedListener {
-        /**
-         * Callback invoked when the current emergency number list has changed on the registered
-         * subscription.
-         *
-         * Note, the registered subscription is associated with {@link TelephonyManager} object
-         * on which
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}
-         * was called.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * given subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param emergencyNumberList Map associating all active subscriptions on the device with
-         *                            the list of emergency numbers originating from that
-         *                            subscription.
-         *                            If there are no active subscriptions, the map will contain a
-         *                            single entry with
-         *                            {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as
-         *                            the key and a list of emergency numbers as the value. If no
-         *                            emergency number information is available, the value will be
-         *                            empty.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-        public void onEmergencyNumberListChanged(
-                @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList);
-    }
-
-    /**
-     * Interface for outgoing emergency call listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface OutgoingEmergencyCallListener {
-        /**
-         * Callback invoked when an outgoing call is placed to an emergency number.
-         *
-         * This method will be called when an emergency call is placed on any subscription
-         * (including the no-SIM case), regardless of which subscription this listener was
-         * registered on.
-         *
-         * The default implementation of this method calls
-         * {@link #onOutgoingEmergencyCall(EmergencyNumber)} for backwards compatibility purposes.
-         * Do not call {@code super(...)} from within your implementation unless you want
-         * {@link #onOutgoingEmergencyCall(EmergencyNumber)} to be called as well.
-         *
-         * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was
-         *                              placed to.
-         * @param subscriptionId The subscription ID used to place the emergency call. If the
-         *                       emergency call was placed without a valid subscription
-         *                       (e.g. when there are no SIM cards in the device), this will be
-         *                       equal to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
-         */
-        @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
-        public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber,
-                                     int subscriptionId);
-    }
-
-    /**
-     * Interface for outgoing emergency sms listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface OutgoingEmergencySmsListener {
-        /**
-         * Smsback invoked when an outgoing sms is sent to an emergency number.
-         *
-         * This method will be called when an emergency sms is sent on any subscription,
-         * regardless of which subscription this listener was registered on.
-         *
-         * The default implementation of this method calls
-         * {@link #onOutgoingEmergencySms(EmergencyNumber)} for backwards compatibility purposes. Do
-         * not call {@code super(...)} from within your implementation unless you want
-         * {@link #onOutgoingEmergencySms(EmergencyNumber)} to be called as well.
-         *
-         * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to.
-         * @param subscriptionId The subscription ID used to send the emergency sms.
-         */
-        @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
-        public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
-                                    int subscriptionId);
-    }
-
-    /**
-     * Interface for phone capability listener.
-     *
-     */
-    public interface PhoneCapabilityChangedListener {
-        /**
-         * Callback invoked when phone capability changes.
-         * Note, this callback triggers regardless of registered subscription.
-         *
-         * @param capability the new phone capability
-         */
-        @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-        public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability);
-    }
-
-    /**
-     * Interface for active data subscription ID listener.
-     */
-    public interface ActiveDataSubscriptionIdChangedListener {
-        /**
-         * Callback invoked when active data subscription ID changes.
-         * Note, this callback triggers regardless of registered subscription.
-         *
-         * @param subId current subscription used to setup Cellular Internet data.
-         *              For example, it could be the current active opportunistic subscription
-         *              in use, or the subscription user selected as default data subscription in
-         *              DSDS mode.
-         */
-        @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-        public void onActiveDataSubscriptionIdChanged(int subId);
-    }
-
-    /**
-     * Interface for modem radio power state listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface RadioPowerStateChangedListener {
-        /**
-         * Callback invoked when modem radio power state changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param state the modem radio power state
-         */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-        public void onRadioPowerStateChanged(@RadioPowerState int state);
-    }
-
-    /**
-     * Interface for carrier network listener.
-     */
-    public interface CarrierNetworkChangeListener {
-        /**
-         * Callback invoked when telephony has received notice from a carrier
-         * app that a network action that could result in connectivity loss
-         * has been requested by an app using
-         * {@link android.service.carrier.CarrierService#notifyCarrierNetworkChange(boolean)}
-         *
-         * This is optional and is only used to allow the system to provide alternative UI while
-         * telephony is performing an action that may result in intentional, temporary network
-         * lack of connectivity.
-         *
-         * Note, this callback is pinned to the registered subscription and will be invoked when
-         * the notifying carrier app has carrier privilege rule on the registered
-         * subscription. {@link android.telephony.TelephonyManager#hasCarrierPrivileges}
-         *
-         * @param active If the carrier network change is or shortly will be active,
-         *               {@code true} indicate that showing alternative UI, {@code false} otherwise.
-         */
-        public void onCarrierNetworkChange(boolean active);
-    }
-
-    /**
-     * Interface for registration failures listener.
-     */
-    public interface RegistrationFailedListener {
-        /**
-         * Report that Registration or a Location/Routing/Tracking Area update has failed.
-         *
-         * <p>Indicate whenever a registration procedure, including a location, routing, or tracking
-         * area update fails. This includes procedures that do not necessarily result in a change of
-         * the modem's registration status. If the modem's registration status changes, that is
-         * reflected in the onNetworkStateChanged() and subsequent
-         * get{Voice/Data}RegistrationState().
-         *
-         * <p>Because registration failures are ephemeral, this callback is not sticky.
-         * Registrants will not receive the most recent past value when registering.
-         *
-         * @param cellIdentity the CellIdentity, which must include the globally unique identifier
-         *        for the cell (for example, all components of the CGI or ECGI).
-         * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
-         *         cell that was chosen for the failed registration attempt.
-         * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
-         * @param causeCode the primary failure cause code of the procedure.
-         *        For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
-         *        For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
-         *        For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
-         *        For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
-         *        Integer.MAX_VALUE if this value is unused.
-         * @param additionalCauseCode the cause code of any secondary/combined procedure
-         *                            if appropriate. For UMTS, if a combined attach succeeds for
-         *                            PS only, then the GMM cause code shall be included as an
-         *                            additionalCauseCode. For LTE (ESM), cause codes are in
-         *                            TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
-         */
-        @RequiresPermission(allOf = {
-                Manifest.permission.READ_PRECISE_PHONE_STATE,
-                Manifest.permission.ACCESS_FINE_LOCATION
-        })
-        public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
-                                         @NonNull String chosenPlmn, @Domain int domain,
-                                         int causeCode, int additionalCauseCode);
-    }
-
-    /**
-     * Interface for call attributes listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface CallAttributesChangedListener {
-        /**
-         * Callback invoked when the call attributes changes on the registered subscription.
-         * Note, the registration subscription ID comes from {@link TelephonyManager} object
-         * which registers PhoneStateListener by
-         * {@link TelephonyManager#registerPhoneStateListener(Executor, PhoneStateListener)}.
-         * If this TelephonyManager object was created with
-         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-         * subscription ID. Otherwise, this callback applies to
-         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-         *
-         * @param callAttributes the call attributes
-         */
-        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-        void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
-    }
-
-    /**
-     * Interface for barring information listener.
-     */
-    public interface BarringInfoChangedListener {
-        /**
-         * Report updated barring information for the current camped/registered cell.
-         *
-         * <p>Barring info is provided for all services applicable to the current camped/registered
-         * cell, for the registered PLMN and current access class/access category.
-         *
-         * @param barringInfo for all services on the current cell.
-         * @see android.telephony.BarringInfo
-         */
-        @RequiresPermission(allOf = {
-                Manifest.permission.READ_PRECISE_PHONE_STATE,
-                Manifest.permission.ACCESS_FINE_LOCATION
-        })
-        public void onBarringInfoChanged(@NonNull BarringInfo barringInfo);
-    }
-
-    /**
-     * Interface for current physical channel configuration listener.
-     */
-    public interface PhysicalChannelConfigChangedListener {
-        /**
-         * Callback invoked when the current physical channel configuration has changed
-         *
-         * @param configs List of the current {@link PhysicalChannelConfig}s
-         */
-        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs);
-    }
-
-    /**
-     * Interface for data enabled listener.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface DataEnabledChangedListener {
-        /**
-         * Callback invoked when the data enabled changes.
-         *
-         * @param enabled {@code true} if data is enabled, otherwise disabled.
-         * @param reason Reason for data enabled/disabled.
-         *               See {@link TelephonyManager.DataEnabledReason}.
-         */
-        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-        public void onDataEnabledChanged(boolean enabled,
-                                  @DataEnabledReason int reason);
+        callback = new IPhoneStateListenerStub(this, e);
     }
 
     /**
@@ -1999,7 +707,6 @@
      * same as above, but with the network type.  Both called.
      */
     public void onDataConnectionStateChanged(int state, int networkType) {
-        // default implementation empty
     }
 
     /**
@@ -2047,7 +754,6 @@
      * @param cellInfo is the list of currently visible cells.
      */
     public void onCellInfoChanged(List<CellInfo> cellInfo) {
-        // default implementation empty
     }
 
     /**
@@ -2061,7 +767,7 @@
      * @param callState {@link PreciseCallState}
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     @SystemApi
     public void onPreciseCallStateChanged(@NonNull PreciseCallState callState) {
         // default implementation empty
@@ -2080,9 +786,9 @@
      * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
      *
      */
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
-    public void onCallDisconnectCauseChanged(@DisconnectCauses int disconnectCause,
-            @PreciseDisconnectCauses int preciseDisconnectCause) {
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
+    public void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause,
+            int preciseDisconnectCause) {
         // default implementation empty
     }
 
@@ -2098,7 +804,7 @@
      * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
      *
      */
-    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) {
         // default implementation empty
     }
@@ -2120,7 +826,7 @@
      *
      * @param dataConnectionState {@link PreciseDataConnectionState}
      */
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
     public void onPreciseDataConnectionStateChanged(
             @NonNull PreciseDataConnectionState dataConnectionState) {
         // default implementation empty
@@ -2158,7 +864,6 @@
      */
     @SystemApi
     public void onSrvccStateChanged(@SrvccState int srvccState) {
-        // default implementation empty
 
     }
 
@@ -2177,7 +882,6 @@
      */
     @SystemApi
     public void onVoiceActivationStateChanged(@SimActivationState int state) {
-        // default implementation empty
     }
 
     /**
@@ -2194,7 +898,6 @@
      * @hide
      */
     public void onDataActivationStateChanged(@SimActivationState int state) {
-        // default implementation empty
     }
 
     /**
@@ -2222,7 +925,7 @@
      *
      * @param telephonyDisplayInfo The display information.
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
     public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
         // default implementation empty
     }
@@ -2355,7 +1058,7 @@
      * @param capability the new phone capability
      * @hide
      */
-    public void onPhoneCapabilityChanged(@NonNull PhoneCapability capability) {
+    public void onPhoneCapabilityChanged(PhoneCapability capability) {
         // default implementation empty
     }
 
@@ -2399,8 +1102,7 @@
      * subId. Otherwise, this callback applies to
      * {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * Requires permission {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
-     *
+     * @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE}
      * @param state the modem radio power state
      * @hide
      */
@@ -2476,6 +1178,18 @@
     }
 
     /**
+     * Callback invoked when the current physical channel configuration has changed
+     *
+     * @param configs List of the current {@link PhysicalChannelConfig}s
+     * @hide
+     */
+    @SystemApi
+    public void onPhysicalChannelConfigurationChanged(
+            @NonNull List<PhysicalChannelConfig> configs) {
+        // default implementation empty
+    }
+
+    /**
      * The callback methods need to be called on the handler thread where
      * this object was created.  If the binder did that for us it'd be nice.
      *
@@ -2498,6 +1212,7 @@
         public void onServiceStateChanged(ServiceState serviceState) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onServiceStateChanged(serviceState)));
         }
@@ -2505,6 +1220,7 @@
         public void onSignalStrengthChanged(int asu) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onSignalStrengthChanged(asu)));
         }
@@ -2512,6 +1228,7 @@
         public void onMessageWaitingIndicatorChanged(boolean mwi) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onMessageWaitingIndicatorChanged(mwi)));
         }
@@ -2519,6 +1236,7 @@
         public void onCallForwardingIndicatorChanged(boolean cfi) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCallForwardingIndicatorChanged(cfi)));
         }
@@ -2530,6 +1248,7 @@
                     cellIdentity == null ? CellLocation.getEmpty() : cellIdentity.asCellLocation();
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCellLocationChanged(location)));
         }
@@ -2537,6 +1256,7 @@
         public void onCallStateChanged(int state, String incomingNumber) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCallStateChanged(state, incomingNumber)));
         }
@@ -2544,6 +1264,7 @@
         public void onDataConnectionStateChanged(int state, int networkType) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             if (state == TelephonyManager.DATA_DISCONNECTING
                     && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
                 Binder.withCleanCallingIdentity(() -> mExecutor.execute(
@@ -2564,6 +1285,7 @@
         public void onDataActivity(int direction) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onDataActivity(direction)));
         }
@@ -2571,6 +1293,7 @@
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
         }
@@ -2578,6 +1301,7 @@
         public void onCellInfoChanged(List<CellInfo> cellInfo) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCellInfoChanged(cellInfo)));
         }
@@ -2585,6 +1309,7 @@
         public void onPreciseCallStateChanged(PreciseCallState callState) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onPreciseCallStateChanged(callState)));
         }
@@ -2592,6 +1317,7 @@
         public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCallDisconnectCauseChanged(
                             disconnectCause, preciseDisconnectCause)));
@@ -2601,6 +1327,7 @@
                 PreciseDataConnectionState dataConnectionState) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onPreciseDataConnectionStateChanged(dataConnectionState)));
@@ -2618,6 +1345,7 @@
         public void onSrvccStateChanged(int state) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onSrvccStateChanged(state)));
         }
@@ -2625,6 +1353,7 @@
         public void onVoiceActivationStateChanged(int activationState) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onVoiceActivationStateChanged(activationState)));
@@ -2633,6 +1362,7 @@
         public void onDataActivationStateChanged(int activationState) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onDataActivationStateChanged(activationState)));
@@ -2641,6 +1371,7 @@
         public void onUserMobileDataStateChanged(boolean enabled) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onUserMobileDataStateChanged(enabled)));
@@ -2649,6 +1380,7 @@
         public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onDisplayInfoChanged(telephonyDisplayInfo)));
@@ -2657,6 +1389,7 @@
         public void onOemHookRawEvent(byte[] rawData) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onOemHookRawEvent(rawData)));
         }
@@ -2664,6 +1397,7 @@
         public void onCarrierNetworkChange(boolean active) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCarrierNetworkChange(active)));
         }
@@ -2671,6 +1405,7 @@
         public void onEmergencyNumberListChanged(Map emergencyNumberList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
@@ -2680,6 +1415,7 @@
                 int subscriptionId) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber,
@@ -2690,6 +1426,7 @@
                 int subscriptionId) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onOutgoingEmergencySms(sentEmergencyNumber, subscriptionId)));
@@ -2698,6 +1435,7 @@
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onPhoneCapabilityChanged(capability)));
         }
@@ -2705,6 +1443,7 @@
         public void onRadioPowerStateChanged(@RadioPowerState int state) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
         }
@@ -2712,6 +1451,7 @@
         public void onCallAttributesChanged(CallAttributes callAttributes) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
         }
@@ -2719,6 +1459,7 @@
         public void onActiveDataSubIdChanged(int subId) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onActiveDataSubscriptionIdChanged(subId)));
         }
@@ -2726,51 +1467,44 @@
         public void onImsCallDisconnectCauseChanged(ImsReasonInfo disconnectCause) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onImsCallDisconnectCauseChanged(disconnectCause)));
+
         }
 
         public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
-                @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+                @NonNull String chosenPlmn, int domain,
+                int causeCode, int additionalCauseCode) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onRegistrationFailed(
-                                cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
+                            cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
             // default implementation empty
         }
 
         public void onBarringInfoChanged(BarringInfo barringInfo) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> psl.onBarringInfoChanged(barringInfo)));
         }
 
-        public void onPhysicalChannelConfigChanged(List<PhysicalChannelConfig> configs) {
+        public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
-            PhysicalChannelConfigChangedListener listener =
-                    (PhysicalChannelConfigChangedListener) mPhoneStateListenerWeakRef.get();
-            if (listener == null) return;
+            if (psl == null) return;
+
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> listener.onPhysicalChannelConfigChanged(
-                            configs)));
-        }
-
-        public void onDataEnabledChanged(boolean enabled, @DataEnabledReason int reason) {
-            if ((mPhoneStateListenerWeakRef.get() instanceof DataEnabledChangedListener)) {
-                DataEnabledChangedListener listener =
-                        (DataEnabledChangedListener) mPhoneStateListenerWeakRef.get();
-                if (listener == null) return;
-
-                Binder.withCleanCallingIdentity(
-                        () -> mExecutor.execute(() -> listener.onDataEnabledChanged(
-                                enabled, reason)));
-            }
+                    () -> mExecutor.execute(
+                            () -> psl.onPhysicalChannelConfigurationChanged(configs)));
         }
     }
 
+
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index c706e21..24ed29a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -15,7 +15,6 @@
  */
 package android.telephony;
 
-import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -25,9 +24,6 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.telephony.Annotation.CallState;
@@ -41,7 +37,6 @@
 import android.telephony.Annotation.SrvccState;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
-import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -50,7 +45,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -212,7 +206,7 @@
     }
 
     /**
-     * To check the SDK version for {@link #listenWithEventList}.
+     * To check the SDK version for {@link #listenForSubscriber}.
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
@@ -224,23 +218,23 @@
      * @param pkg Package name
      * @param featureId Feature ID
      * @param listener Listener providing callback
-     * @param events List events
+     * @param events Events
      * @param notifyNow Whether to notify instantly
      */
-    public void listenWithEventList(int subId, @NonNull String pkg, @NonNull String featureId,
-            @NonNull PhoneStateListener listener, @NonNull int[] events, boolean notifyNow) {
+    public void listenForSubscriber(int subId, @NonNull String pkg, @NonNull String featureId,
+            @NonNull PhoneStateListener listener, long events, boolean notifyNow) {
         try {
             // subId from PhoneStateListener is deprecated Q on forward, use the subId from
             // TelephonyManager instance. Keep using subId from PhoneStateListener for pre-Q.
             if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) {
                 // Since mSubId in PhoneStateListener is deprecated from Q on forward, this is
                 // the only place to set mSubId and its for "informational" only.
-                listener.mSubId = (events.length == 0)
+                listener.mSubId = (events == PhoneStateListener.LISTEN_NONE)
                         ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId;
             } else if (listener.mSubId != null) {
                 subId = listener.mSubId;
             }
-            sRegistry.listenWithEventList(
+            sRegistry.listenForSubscriber(
                     subId, pkg, featureId, listener.callback, events, notifyNow);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -777,349 +771,13 @@
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
+    public void notifyPhysicalChannelConfigurationForSubscriber(
             int subId, List<PhysicalChannelConfig> configs) {
         try {
-            sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs);
+            sRegistry.notifyPhysicalChannelConfigurationForSubscriber(subId, configs);
         } catch (RemoteException ex) {
             // system server crash
         }
     }
 
-    public @NonNull Set<Integer> getEventsFromListener(@NonNull PhoneStateListener listener) {
-
-        Set<Integer> eventList = new ArraySet<>();
-
-        if (listener instanceof PhoneStateListener.ServiceStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.MessageWaitingIndicatorChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CallForwardingIndicatorChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CellLocationChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CallStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.DataConnectionStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.DataActivityListener) {
-            eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.SignalStrengthsChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.AlwaysReportedSignalStrengthsChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CellInfoChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.PreciseCallStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CallDisconnectCauseChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.ImsCallDisconnectCauseChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.PreciseDataConnectionStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.SrvccStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.VoiceActivationStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.DataActivationStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.UserMobileDataStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.DisplayInfoChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.EmergencyNumberListChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.OutgoingEmergencyCallListener) {
-            eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL);
-        }
-
-        if (listener instanceof PhoneStateListener.OutgoingEmergencySmsListener) {
-            eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
-        }
-
-        if (listener instanceof PhoneStateListener.PhoneCapabilityChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.ActiveDataSubscriptionIdChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.RadioPowerStateChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.CarrierNetworkChangeListener) {
-            eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.RegistrationFailedListener) {
-            eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
-        }
-
-        if (listener instanceof PhoneStateListener.CallAttributesChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.BarringInfoChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.PhysicalChannelConfigChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-        }
-
-        if (listener instanceof PhoneStateListener.DataEnabledChangedListener) {
-            eventList.add(PhoneStateListener.EVENT_DATA_ENABLED_CHANGED);
-        }
-
-        return eventList;
-    }
-
-    private @NonNull Set<Integer> getEventsFromBitmask(int eventMask) {
-
-        Set<Integer> eventList = new ArraySet<>();
-
-        if ((eventMask & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
-            eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
-            eventList.add(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CALL_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
-            eventList.add(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
-            eventList.add(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
-            eventList.add(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CELL_INFO) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CELL_INFO_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO) != 0) {
-            eventList.add(PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
-            eventList.add(PhoneStateListener.EVENT_OEM_HOOK_RAW);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) != 0) {
-            eventList.add(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
-            eventList.add(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
-            eventList.add(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
-            eventList.add(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
-            eventList.add(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) {
-            eventList.add(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL) != 0) {
-            eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS) != 0) {
-            eventList.add(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_REGISTRATION_FAILURE) != 0) {
-            eventList.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
-        }
-
-        if ((eventMask & PhoneStateListener.LISTEN_BARRING_INFO) != 0) {
-            eventList.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
-        }
-        return eventList;
-
-    }
-
-    /**
-     * Registers a listener object to receive notification of changes
-     * in specified telephony states.
-     * <p>
-     * To register a listener, pass a {@link PhoneStateListener} which implements
-     * interfaces of events. For example,
-     * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements
-     * {@link PhoneStateListener.ServiceStateChangedListener}.
-     *
-     * At registration, and when a specified telephony state changes, the telephony manager invokes
-     * the appropriate callback method on the listener object and passes the current (updated)
-     * values.
-     * <p>
-     *
-     * If this TelephonyManager object has been created with
-     * {@link TelephonyManager#createForSubscriptionId}, applies to the given subId.
-     * Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
-     * To listen events for multiple subIds, pass a separate listener object to
-     * each TelephonyManager object created with {@link TelephonyManager#createForSubscriptionId}.
-     *
-     * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
-     * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
-     * {@link SecurityException} will be thrown otherwise.
-     *
-     * This API should be used sparingly -- large numbers of listeners will cause system
-     * instability. If a process has registered too many listeners without unregistering them, it
-     * may encounter an {@link IllegalStateException} when trying to register more listeners.
-     *
-     * @param listener The {@link PhoneStateListener} object to register.
-     */
-    public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, int subId,
-            String pkgName, String attributionTag, @NonNull PhoneStateListener listener,
-            boolean notifyNow) {
-        registerPhoneStateListener(executor, subId, pkgName, attributionTag, listener,
-                getEventsFromListener(listener), notifyNow);
-    }
-
-    public void registerPhoneStateListenerWithEvents(int subId, String pkgName,
-            String attributionTag, @NonNull PhoneStateListener listener, int events,
-            boolean notifyNow) {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        registerPhoneStateListener(new HandlerExecutor(new Handler(Looper.myLooper())),
-                subId, pkgName, attributionTag, listener, getEventsFromBitmask(events), notifyNow);
-    }
-
-    private void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor, int subId,
-            String pkgName, String attributionTag, @NonNull PhoneStateListener listener,
-            @NonNull Set<Integer> events, boolean notifyNow) {
-        if (listener == null) {
-            throw new IllegalStateException("telephony service is null.");
-        }
-
-        listener.setExecutor(executor);
-        listenWithEventList(subId, pkgName, attributionTag, listener,
-                events.stream().mapToInt(i -> i).toArray(), notifyNow);
-    }
-
-    /**
-     * Unregister an existing {@link PhoneStateListener}.
-     *
-     * @param listener The {@link PhoneStateListener} object to unregister.
-     */
-    public void unregisterPhoneStateListener(int subId, String pkgName, String attributionTag,
-                                             @NonNull PhoneStateListener listener,
-                                             boolean notifyNow) {
-        listenWithEventList(subId, pkgName, attributionTag, listener, new int[0], notifyNow);
-    }
 }
diff --git a/core/java/android/uwb/AngleOfArrivalSupport.aidl b/core/java/android/uwb/AngleOfArrivalSupport.aidl
new file mode 100644
index 0000000..57666ff
--- /dev/null
+++ b/core/java/android/uwb/AngleOfArrivalSupport.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum AngleOfArrivalSupport {
+  /**
+   * The device does not support angle of arrival
+   */
+  NONE,
+
+  /**
+   * The device supports planar angle of arrival
+   */
+  TWO_DIMENSIONAL,
+
+  /**
+   * The device does supports three dimensional angle of arrival with hemispherical azimuth angles
+   */
+  THREE_DIMENSIONAL_HEMISPHERICAL,
+
+  /**
+   * The device does supports three dimensional angle of arrival with full azimuth angles
+   */
+  THREE_DIMENSIONAL_SPHERICAL,
+}
+
diff --git a/core/java/android/uwb/CloseReason.aidl b/core/java/android/uwb/CloseReason.aidl
new file mode 100644
index 0000000..bef129e
--- /dev/null
+++ b/core/java/android/uwb/CloseReason.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum CloseReason {
+  /**
+   * Unknown reason
+   */
+  UNKNOWN,
+
+  /**
+   * A local API call triggered the close, such as a call to
+   * IUwbAdapter.stopRanging.
+   */
+  LOCAL_API,
+
+  /**
+   * The maximum number of sessions has been reached. This error may be generated
+   * for an active session if a higher priority session begins.
+   */
+  MAX_SESSIONS_REACHED,
+
+  /**
+   * The system state has changed resulting in the session ending (e.g. the user
+   * disables UWB, or the user's locale changes and an active channel is no longer
+   * permitted to be used).
+   */
+  SYSTEM_POLICY,
+
+  /**
+   * The remote device has requested to terminate the session
+   */
+  REMOTE_REQUEST,
+
+  /**
+   * The session was closed for a protocol specific reason
+   */
+  PROTOCOL_SPECIFIC,
+}
+
diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl
new file mode 100644
index 0000000..d29ed34
--- /dev/null
+++ b/core/java/android/uwb/IUwbAdapter.aidl
@@ -0,0 +1,170 @@
+/*
+ * 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.uwb;
+
+import android.os.PersistableBundle;
+import android.uwb.AngleOfArrivalSupport;
+import android.uwb.IUwbAdapterStateCallbacks;
+import android.uwb.IUwbRangingCallbacks;
+import android.uwb.SessionHandle;
+
+/**
+ * @hide
+ */
+interface IUwbAdapter {
+  /*
+   * Register the callbacks used to notify the framework of events and data
+   *
+   * The provided callback's IUwbAdapterStateCallbacks#onAdapterStateChanged
+   * function must be called immediately following registration with the current
+   * state of the UWB adapter.
+   *
+   * @param callbacks callback to provide range and status updates to the framework
+   */
+  void registerAdapterStateCallbacks(in IUwbAdapterStateCallbacks adapterStateCallbacks);
+
+  /*
+   * Unregister the callbacks used to notify the framework of events and data
+   *
+   * Calling this function with an unregistered callback is a no-op
+   *
+   * @param callbacks callback to unregister
+   */
+  void unregisterAdapterStateCallbacks(in IUwbAdapterStateCallbacks callbacks);
+
+  /**
+   * Returns true if ranging is supported, false otherwise
+   */
+  boolean isRangingSupported();
+
+  /**
+   * Get the angle of arrival supported by this device
+   *
+   * @return the angle of arrival type supported
+   */
+  AngleOfArrivalSupport getAngleOfArrivalSupport();
+
+  /**
+   * Generates a list of the supported 802.15.4z channels
+   *
+   * The list must be prioritized in the order of preferred channel usage.
+   *
+   * The list must only contain channels that are permitted to be used in the
+   * device's current location.
+   *
+   * @return an array of support channels on the device for the current location.
+   */
+  int[] getSupportedChannels();
+
+  /**
+   * Generates a list of the supported 802.15.4z preamble codes
+   *
+   * The list must be prioritized in the order of preferred preamble usage.
+   *
+   * The list must only contain preambles that are permitted to be used in the
+   * device's current location.
+   *
+   * @return an array of supported preambles on the device for the current
+   *         location.
+   */
+  int[] getSupportedPreambleCodes();
+
+  /**
+   * Get the accuracy of the ranging timestamps
+   *
+   * @return accuracy of the ranging timestamps in nanoseconds
+   */
+  long getTimestampResolutionNanos();
+
+  /**
+   * Get the supported number of simultaneous ranging sessions
+   *
+   * @return the supported number of simultaneous ranging sessions
+   */
+  int getMaxSimultaneousSessions();
+
+  /**
+   * Get the maximum number of remote devices per session
+   *
+   * @return the maximum number of remote devices supported in a single session
+   */
+  int getMaxRemoteDevicesPerSession();
+
+  /**
+   * Provides the capabilities and features of the device
+   *
+   * @return specification specific capabilities and features of the device
+   */
+  PersistableBundle getSpecificationInfo();
+
+  /**
+   * Request to start a new ranging session
+   *
+   * This function must return before calling IUwbAdapterCallbacks
+   * #onRangingStarted, #onRangingClosed, or #onRangingResult.
+   *
+   * A ranging session does not need to be started before returning.
+   *
+   * IUwbAdapterCallbacks#onRangingStarted must be called within
+   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
+   * if the ranging session is scheduled to start successfully.
+   *
+   * IUwbAdapterCallbacks#onRangingStartFailed must be called within
+   * RANGING_SESSION_START_THRESHOLD_MS milliseconds of #startRanging being called
+   * if the ranging session fails to be scheduled to start successfully.
+   *
+   * @param rangingCallbacks the callbacks used to deliver ranging information
+   * @param parameters the configuration to use for ranging
+   * @return a SessionHandle used to identify this ranging request
+   */
+  SessionHandle startRanging(in IUwbRangingCallbacks rangingCallbacks,
+                             in PersistableBundle parameters);
+
+  /**
+   * Stop and close ranging for the session associated with the given handle
+   *
+   * Calling with an invalid handle or a handle that has already been closed
+   * is a no-op.
+   *
+   * IUwbAdapterCallbacks#onRangingClosed must be called within
+   * RANGING_SESSION_CLOSE_THRESHOLD_MS of #stopRanging being called.
+   *
+   * @param sessionHandle the session handle to stop ranging for
+   */
+  void closeRanging(in SessionHandle sessionHandle);
+
+  /**
+   * The maximum allowed time to start a ranging session.
+   */
+  const int RANGING_SESSION_START_THRESHOLD_MS = 3000; // Value TBD
+
+  /**
+   * The maximum allowed time to notify the framework that a session has been
+   * closed.
+   */
+  const int RANGING_SESSION_CLOSE_THRESHOLD_MS = 3000; // Value TBD
+
+  /**
+   * Ranging scheduling time unit (RSTU) for High Rate Pulse (HRP) PHY
+   */
+  const int HIGH_RATE_PULSE_CHIRPS_PER_RSTU = 416;
+
+  /**
+   * Ranging scheduling time unit (RSTU) for Low Rate Pulse (LRP) PHY
+   */
+  const int LOW_RATE_PULSE_CHIRPS_PER_RSTU = 1;
+}
diff --git a/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl b/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl
new file mode 100644
index 0000000..d928eab
--- /dev/null
+++ b/core/java/android/uwb/IUwbAdapterStateCallbacks.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.uwb;
+
+import android.uwb.StateChangeReason;
+
+/**
+ * @hide
+ */
+interface IUwbAdapterStateCallbacks {
+  /**
+   * Called whenever the adapter state changes
+   *
+   * @param isEnabled true if the adapter is enabled, false otherwise
+   * @param reason the reason that the state has changed
+   */
+  void onAdapterStateChanged(boolean isEnabled, StateChangeReason reason);
+}
\ No newline at end of file
diff --git a/core/java/android/uwb/IUwbRangingCallbacks.aidl b/core/java/android/uwb/IUwbRangingCallbacks.aidl
new file mode 100644
index 0000000..1fc3bfd
--- /dev/null
+++ b/core/java/android/uwb/IUwbRangingCallbacks.aidl
@@ -0,0 +1,73 @@
+/*
+ * 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.uwb;
+
+import android.os.PersistableBundle;
+import android.uwb.CloseReason;
+import android.uwb.RangingReport;
+import android.uwb.SessionHandle;
+import android.uwb.StartFailureReason;
+
+/**
+ * @hide
+ */
+interface IUwbRangingCallbacks {
+  /**
+   * Called when ranging has started
+   *
+   * May output parameters generated by the lower layers that must be sent to the
+   * remote device(s). The PersistableBundle must be constructed using the UWB
+   * support library.
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param rangingOutputParameters parameters generated by the lower layer that
+   *                                should be sent to the remote device.
+   */
+  void onRangingStarted(in SessionHandle sessionHandle,
+                        in PersistableBundle parameters);
+
+  /**
+   * Called when a ranging session fails to start
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param reason the reason the session failed to start
+   * @param parameters protocol specific parameters
+   */
+  void onRangingStartFailed(in SessionHandle sessionHandle, StartFailureReason reason,
+                            in PersistableBundle parameters);
+  /**
+   * Called when a ranging session is closed
+   *
+   * @param sessionHandle the session the callback is being invoked for
+   * @param reason the reason the session was closed
+   * @param parameters protocol specific parameters
+   */
+  void onRangingClosed(in SessionHandle sessionHandle, CloseReason reason,
+                       in PersistableBundle parameters);
+
+  /**
+   * Provides a new RangingResult to the framework
+   *
+   * The reported timestamp for a ranging measurement must be calculated as the
+   * time which the ranging round that generated this measurement concluded.
+   *
+   * @param sessionHandle an identifier to associate the ranging results with a
+   *                      session that is active
+   * @param result the ranging report
+   */
+  void onRangingResult(in SessionHandle sessionHandle, in RangingReport result);
+}
diff --git a/core/java/android/uwb/MeasurementStatus.aidl b/core/java/android/uwb/MeasurementStatus.aidl
new file mode 100644
index 0000000..5fa1554
--- /dev/null
+++ b/core/java/android/uwb/MeasurementStatus.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum MeasurementStatus {
+  /**
+   * Ranging was successful
+   */
+  SUCCESS,
+
+  /**
+   * The remote device is out of range
+   */
+  FAILURE_OUT_OF_RANGE,
+
+  /**
+   * An unknown failure has occurred.
+   */
+   FAILURE_UNKNOWN,
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/RangingReport.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/RangingReport.aidl
index d7a3af0..c32747a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/RangingReport.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,13 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.classifier;
+package android.uwb;
 
-public class PointerCountEvaluator {
-    public static float evaluate(int value) {
-        return (value - 1) * (value - 1);
-    }
-}
+parcelable RangingReport;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/SessionHandle.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/SessionHandle.aidl
index d7a3af0..58a7dbb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/SessionHandle.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,13 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.classifier;
+package android.uwb;
 
-public class PointerCountEvaluator {
-    public static float evaluate(int value) {
-        return (value - 1) * (value - 1);
-    }
-}
+parcelable SessionHandle;
diff --git a/core/java/android/uwb/SessionHandle.java b/core/java/android/uwb/SessionHandle.java
new file mode 100644
index 0000000..928fcbdc
--- /dev/null
+++ b/core/java/android/uwb/SessionHandle.java
@@ -0,0 +1,79 @@
+/*
+ * 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.uwb;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SessionHandle implements Parcelable  {
+    private final int mId;
+
+    public SessionHandle(int id) {
+        mId = id;
+    }
+
+    protected SessionHandle(Parcel in) {
+        mId = in.readInt();
+    }
+
+    public static final Creator<SessionHandle> CREATOR = new Creator<SessionHandle>() {
+        @Override
+        public SessionHandle createFromParcel(Parcel in) {
+            return new SessionHandle(in);
+        }
+
+        @Override
+        public SessionHandle[] newArray(int size) {
+            return new SessionHandle[size];
+        }
+    };
+
+    public int getId() {
+        return mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof SessionHandle) {
+            SessionHandle other = (SessionHandle) obj;
+            return mId == other.mId;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "SessionHandle [id=" + mId + "]";
+    }
+}
diff --git a/core/java/android/uwb/StartFailureReason.aidl b/core/java/android/uwb/StartFailureReason.aidl
new file mode 100644
index 0000000..4d9c962
--- /dev/null
+++ b/core/java/android/uwb/StartFailureReason.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StartFailureReason {
+  /**
+   * Unknown start failure reason
+   */
+  UNKNOWN,
+
+  /**
+   * The provided parameters were invalid and ranging could not start
+   */
+  BAD_PARAMETERS,
+
+  /**
+   * The maximum number of sessions has been reached. This error may be generated
+   * for an active session if a higher priority session begins.
+   */
+  MAX_SESSIONS_REACHED,
+
+  /**
+   * The system state has changed resulting in the session ending (e.g. the user
+   * disables UWB, or the user's locale changes and an active channel is no longer
+   * permitted to be used).
+   */
+  SYSTEM_POLICY,
+
+  /**
+   * The session could not start because of a protocol specific reason.
+   */
+  PROTOCOL_SPECIFIC,
+}
+
diff --git a/core/java/android/uwb/StateChangeReason.aidl b/core/java/android/uwb/StateChangeReason.aidl
new file mode 100644
index 0000000..46a6e2e
--- /dev/null
+++ b/core/java/android/uwb/StateChangeReason.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.uwb;
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StateChangeReason {
+  /**
+   * The state changed for an unknown reason
+   */
+  UNKNOWN,
+
+  /**
+   * The adapter state changed because a session started.
+   */
+  SESSION_STARTED,
+
+
+  /**
+   * The adapter state changed because all sessions were closed.
+   */
+  ALL_SESSIONS_CLOSED,
+
+  /**
+   * The adapter state changed because of a device system change.
+   */
+  SYSTEM_POLICY,
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/core/java/android/uwb/UwbAddress.aidl
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
copy to core/java/android/uwb/UwbAddress.aidl
index d7a3af0..a202b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/core/java/android/uwb/UwbAddress.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,13 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.classifier;
+package android.uwb;
 
-public class PointerCountEvaluator {
-    public static float evaluate(int value) {
-        return (value - 1) * (value - 1);
-    }
-}
+parcelable UwbAddress;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 924fc6d..0533533 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -668,22 +668,24 @@
     void setShouldShowIme(int displayId, boolean shouldShow);
 
     /**
-     * Waits for transactions to get applied before injecting input.
-     * This includes waiting for the input windows to get sent to InputManager.
+     * Waits for transactions to get applied before injecting input, optionally waiting for
+     * animations to complete. This includes waiting for the input windows to get sent to
+     * InputManager.
      *
      * This is needed for testing since the system add windows and injects input
      * quick enough that the windows don't have time to get sent to InputManager.
      */
-    boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode);
+    boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode,
+            boolean waitForAnimations);
 
     /**
-     * Waits until all animations have completed and input information has been sent from
-     * WindowManager to native InputManager.
+     * Waits until input information has been sent from WindowManager to native InputManager,
+     * optionally waiting for animations to complete.
      *
      * This is needed for testing since we need to ensure input information has been propagated to
      * native InputManager before proceeding with tests.
      */
-    void syncInputTransactions();
+    void syncInputTransactions(boolean waitForAnimations);
 
     /**
      * Returns whether SurfaceFlinger layer tracing is enabled.
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 038dff6..7d1adc36 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -144,6 +144,20 @@
     }
 
     /**
+     * Called when a Pointer Capture event is received.
+     *
+     * @param pointerCaptureEnabled if true, the window associated with this input channel has just
+     *                              received Pointer Capture
+     *                              if false, the window associated with this input channel has just
+     *                              lost Pointer Capture
+     * @see View#requestPointerCapture()
+     * @see View#releasePointerCapture()
+     */
+    // Called from native code.
+    public void onPointerCaptureEvent(boolean pointerCaptureEnabled) {
+    }
+
+    /**
      * Called when a batched input event is pending.
      *
      * The batched input event will continue to accumulate additional movement
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index f7fbb1c..673ed0d 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -43,12 +43,13 @@
  */
 @RemoteViews.RemoteView
 public class NotificationHeaderView extends FrameLayout {
-    private final int mContentEndMargin;
     private final int mHeadingEndMargin;
+    private final int mTouchableHeight;
     private OnClickListener mExpandClickListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
     private NotificationTopLineView mTopLineView;
     private NotificationExpandButton mExpandButton;
+    private View mAltExpandTarget;
     private CachingIconView mIcon;
     private Drawable mBackground;
     private boolean mEntireHeaderClickable;
@@ -82,8 +83,8 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         Resources res = getResources();
-        mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
         mHeadingEndMargin = res.getDimensionPixelSize(R.dimen.notification_heading_margin_end);
+        mTouchableHeight = res.getDimensionPixelSize(R.dimen.notification_header_touchable_height);
         mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
     }
 
@@ -93,6 +94,7 @@
         mIcon = findViewById(R.id.icon);
         mTopLineView = findViewById(R.id.notification_top_line);
         mExpandButton = findViewById(R.id.expand_button);
+        mAltExpandTarget = findViewById(R.id.alternate_expand_target);
         setClipToPadding(false);
     }
 
@@ -146,6 +148,7 @@
     public void setOnClickListener(@Nullable OnClickListener l) {
         mExpandClickListener = l;
         mExpandButton.setOnClickListener(mExpandClickListener);
+        mAltExpandTarget.setOnClickListener(mExpandClickListener);
         updateTouchListener();
     }
 
@@ -187,6 +190,7 @@
 
         private final ArrayList<Rect> mTouchRects = new ArrayList<>();
         private Rect mExpandButtonRect;
+        private Rect mAltExpandTargetRect;
         private int mTouchSlop;
         private boolean mTrackGesture;
         private float mDownX;
@@ -199,6 +203,7 @@
             mTouchRects.clear();
             addRectAroundView(mIcon);
             mExpandButtonRect = addRectAroundView(mExpandButton);
+            mAltExpandTargetRect = addRectAroundView(mAltExpandTarget);
             addWidthRect();
             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         }
@@ -206,7 +211,7 @@
         private void addWidthRect() {
             Rect r = new Rect();
             r.top = 0;
-            r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+            r.bottom = mTouchableHeight;
             r.left = 0;
             r.right = getWidth();
             mTouchRects.add(r);
@@ -277,7 +282,8 @@
                 return true;
             }
             if (mExpandOnlyOnButton) {
-                return mExpandButtonRect.contains((int) x, (int) y);
+                return mExpandButtonRect.contains((int) x, (int) y)
+                        || mAltExpandTargetRect.contains((int) x, (int) y);
             }
             for (int i = 0; i < mTouchRects.size(); i++) {
                 Rect r = mTouchRects.get(i);
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index a8eabe5..05636de 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -97,10 +97,8 @@
         final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
         final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
         final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
-        int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
-                MeasureSpec.AT_MOST);
-        int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
-                MeasureSpec.AT_MOST);
+        int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, MeasureSpec.AT_MOST);
+        int heightSpec = MeasureSpec.makeMeasureSpec(givenHeight, MeasureSpec.AT_MOST);
         int totalWidth = getPaddingStart();
         int maxChildHeight = -1;
         mMaxAscent = -1;
@@ -114,7 +112,7 @@
             final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
             int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
                     lp.leftMargin + lp.rightMargin, lp.width);
-            int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+            int childHeightSpec = getChildMeasureSpec(heightSpec,
                     lp.topMargin + lp.bottomMargin, lp.height);
             child.measure(childWidthSpec, childHeightSpec);
             totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -131,37 +129,37 @@
         int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
         if (totalWidth > givenWidth - endMargin) {
             int overFlow = totalWidth - givenWidth + endMargin;
-            if (mAppName != null) {
-                // We are overflowing, lets shrink the app name first
-                overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
-                        mChildMinWidth);
-            }
 
-            if (mTitle != null) {
-                // still overflowing, we shrink the title text
-                overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTitle,
-                        mChildMinWidth);
-            }
+            // First shrink the app name, down to a minimum size
+            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
 
-            // still overflowing, we shrink the header text
-            overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
+            // Next, shrink the header text (this usually has subText)
+            //   This shrinks the subtext first, but not all the way (yet!)
+            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
 
-            // still overflowing, finally we shrink the secondary header text
-            shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
-                    0);
+            // Next, shrink the secondary header text  (this rarely has conversationTitle)
+            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
+
+            // Next, shrink the title text (this has contentTitle; only in headerless views)
+            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
+
+            // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
+            shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
         }
         setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
     }
 
     private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
             int minimumWidth) {
-        final int oldWidth = targetView.getMeasuredWidth();
-        if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
-            // we're still too big
-            int newSize = Math.max(minimumWidth, oldWidth - overFlow);
-            int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
-            targetView.measure(childWidthSpec, heightSpec);
-            overFlow -= oldWidth - newSize;
+        if (targetView != null) {
+            final int oldWidth = targetView.getMeasuredWidth();
+            if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
+                // we're still too big
+                int newSize = Math.max(minimumWidth, oldWidth - overFlow);
+                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+                targetView.measure(childWidthSpec, heightSpec);
+                overFlow -= oldWidth - newSize;
+            }
         }
         return overFlow;
     }
diff --git a/core/java/android/view/OnReceiveContentListener.java b/core/java/android/view/OnReceiveContentListener.java
index 4955289..db9c538 100644
--- a/core/java/android/view/OnReceiveContentListener.java
+++ b/core/java/android/view/OnReceiveContentListener.java
@@ -48,7 +48,7 @@
  *     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
  *
  *     &#64;Override
- *     public Payload onReceiveContent(TextView view, Payload payload) {
+ *     public Payload onReceiveContent(View view, Payload payload) {
  *         Map&lt;Boolean, Payload&gt; split = payload.partition(item -&gt; item.getUri() != null);
  *         if (split.get(true) != null) {
  *             ClipData clip = payload.getClip();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a88ad9f..30ec2b0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9008,7 +9008,7 @@
     }
 
     /**
-     * Sets the listener to be {@link #onReceiveContent used} to handle insertion of
+     * Sets the listener to be {@link #performReceiveContent used} to handle insertion of
      * content into this view.
      *
      * <p>Depending on the type of view, this listener may be invoked for different scenarios. For
@@ -9039,7 +9039,6 @@
      *                  not be null or empty if a non-null listener is passed in.
      * @param listener The listener to use. This can be null to reset to the default behavior.
      */
-    @SuppressWarnings("rawtypes")
     public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
             @Nullable OnReceiveContentListener listener) {
         if (listener != null) {
@@ -9055,27 +9054,46 @@
     }
 
     /**
-     * Receives the given content. Invokes the listener configured via
-     * {@link #setOnReceiveContentListener}; if no listener is set, the default implementation is a
-     * no-op (returns the passed-in content without acting on it).
+     * Receives the given content. If no listener is set, invokes {@link #onReceiveContent}. If a
+     * listener is {@link #setOnReceiveContentListener set}, invokes the listener instead; if the
+     * listener returns a non-null result, invokes {@link #onReceiveContent} to handle it.
      *
      * @param payload The content to insert and related metadata.
      *
      * @return The portion of the passed-in content that was not accepted (may be all, some, or none
      * of the passed-in content).
      */
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
+    public @Nullable Payload performReceiveContent(@NonNull Payload payload) {
         final OnReceiveContentListener listener = (mListenerInfo == null) ? null
                 : getListenerInfo().mOnReceiveContentListener;
         if (listener != null) {
-            return listener.onReceiveContent(this, payload);
+            final Payload remaining = listener.onReceiveContent(this, payload);
+            return (remaining == null) ? null : onReceiveContent(remaining);
         }
+        return onReceiveContent(payload);
+    }
+
+    /**
+     * Implements the default behavior for receiving content for this type of view. The default
+     * view implementation is a no-op (returns the passed-in content without acting on it).
+     *
+     * <p>Widgets should override this method to define their default behavior for receiving
+     * content. Apps should {@link #setOnReceiveContentListener set a listener} to provide
+     * app-specific handling for receiving content.
+     *
+     * <p>See {@link #setOnReceiveContentListener} and {@link #performReceiveContent} for more info.
+     *
+     * @param payload The content to insert and related metadata.
+     *
+     * @return The portion of the passed-in content that was not handled (may be all, some, or none
+     * of the passed-in content).
+     */
+    public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
         return payload;
     }
 
     /**
-     * Returns the MIME types accepted by {@link #onReceiveContent} for this view, as
+     * Returns the MIME types accepted by {@link #performReceiveContent} for this view, as
      * configured via {@link #setOnReceiveContentListener}. By default returns null.
      *
      * <p>Different features (e.g. pasting from the clipboard, inserting stickers from the soft
@@ -9092,7 +9110,7 @@
      * {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to
      * lowercase.
      *
-     * @return The MIME types accepted by {@link #onReceiveContent} for this view (may
+     * @return The MIME types accepted by {@link #performReceiveContent} for this view (may
      * include patterns such as "image/*").
      */
     public @Nullable String[] getOnReceiveContentMimeTypes() {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 299c41b..8fdcac7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2372,7 +2372,7 @@
                 return;
             }
             Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build();
-            Payload result = view.onReceiveContent(payload);
+            Payload result = view.performReceiveContent(payload);
             if (result != null) {
                 Log.w(TAG, "autofillContent(): receiver could not insert content: id=" + id
                         + ", view=" + view + ", clip=" + clip);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a92d1f5..1ab9edf 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -43,6 +43,8 @@
 import android.view.OnReceiveContentListener;
 import android.view.View;
 
+import com.android.internal.util.Preconditions;
+
 class ComposingText implements NoCopySpan {
 }
 
@@ -504,7 +506,7 @@
      */
     @Nullable
     public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) {
-        if (length < 0) return null;
+        Preconditions.checkArgumentNonnegative(length);
 
         final Editable content = getEditable();
         if (content == null) return null;
@@ -563,7 +565,7 @@
      */
     @Nullable
     public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) {
-        if (length < 0) return null;
+        Preconditions.checkArgumentNonnegative(length);
 
         final Editable content = getEditable();
         if (content == null) return null;
@@ -600,7 +602,8 @@
     @Nullable
     public SurroundingText getSurroundingText(
             @IntRange(from = 0) int beforeLength, @IntRange(from = 0)  int afterLength, int flags) {
-        if (beforeLength < 0 || afterLength < 0) return null;
+        Preconditions.checkArgumentNonnegative(beforeLength);
+        Preconditions.checkArgumentNonnegative(afterLength);
 
         final Editable content = getEditable();
         if (content == null) return null;
@@ -927,9 +930,9 @@
     }
 
     /**
-     * Default implementation which invokes {@link View#onReceiveContent} on the target view if the
-     * view {@link View#getOnReceiveContentMimeTypes allows} content insertion; otherwise returns
-     * false without any side effects.
+     * Default implementation which invokes {@link View#performReceiveContent} on the target
+     * view if the view {@link View#getOnReceiveContentMimeTypes allows} content insertion;
+     * otherwise returns false without any side effects.
      */
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
         ClipDescription description = inputContentInfo.getDescription();
@@ -954,6 +957,6 @@
                 .setLinkUri(inputContentInfo.getLinkUri())
                 .setExtras(opts)
                 .build();
-        return mTargetView.onReceiveContent(payload) == null;
+        return mTargetView.performReceiveContent(payload) == null;
     }
 }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index da14f2c..00ba326 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2872,7 +2872,7 @@
             final OnReceiveContentListener.Payload payload =
                     new OnReceiveContentListener.Payload.Builder(clip, SOURCE_DRAG_AND_DROP)
                     .build();
-            mTextView.onReceiveContent(payload);
+            mTextView.performReceiveContent(payload);
             if (dragDropIntoItself) {
                 deleteSourceAfterLocalDrop(dragLocalState, offset, originalLength);
             }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fb13807..98f8087 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2152,7 +2152,7 @@
                     if (isTextEditable()) {
                         ClipData clip = ClipData.newPlainText("", result);
                         Payload payload = new Payload.Builder(clip, SOURCE_PROCESS_TEXT).build();
-                        onReceiveContent(payload);
+                        performReceiveContent(payload);
                         if (mEditor != null) {
                             mEditor.refreshTextActionMode();
                         }
@@ -11858,7 +11858,7 @@
             return;
         }
         final Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build();
-        onReceiveContent(payload);
+        performReceiveContent(payload);
     }
 
     @Override
@@ -12927,7 +12927,7 @@
         final Payload payload = new Payload.Builder(clip, SOURCE_CLIPBOARD)
                 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
                 .build();
-        onReceiveContent(payload);
+        performReceiveContent(payload);
         sLastCutCopyOrTextChangedTime = 0;
     }
 
@@ -13728,17 +13728,12 @@
     }
 
     /**
-     * Receives the given content. Clients wishing to provide custom behavior should configure a
-     * listener via {@link #setOnReceiveContentListener}.
+     * Default {@link TextView} implementation for receiving content. Apps wishing to provide
+     * custom behavior should configure a listener via {@link #setOnReceiveContentListener}.
      *
-     * <p>If a listener is set, invokes the listener. If the listener returns a non-null result,
-     * executes the default platform handling for the portion of the content returned by the
-     * listener.
-     *
-     * <p>If no listener is set, executes the default platform behavior. For non-editable TextViews
-     * the default behavior is a no-op (returns the passed-in content without acting on it). For
-     * editable TextViews the default behavior coerces all content to text and inserts into the
-     * view.
+     * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in
+     * content without acting on it). For editable TextViews the default behavior coerces all
+     * content to text and inserts into the view.
      *
      * @param payload The content to insert and related metadata.
      *
@@ -13747,11 +13742,10 @@
      */
     @Override
     public @Nullable Payload onReceiveContent(@NonNull Payload payload) {
-        Payload remaining = super.onReceiveContent(payload);
-        if (remaining != null && mEditor != null) {
-            return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, remaining);
+        if (mEditor != null) {
+            return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload);
         }
-        return remaining;
+        return payload;
     }
 
     private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index e6a1661..6d98a59 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -52,7 +52,7 @@
     @UnsupportedAppUsage
     byte[] getStatistics();
 
-    ParcelFileDescriptor getStatisticsStream();
+    ParcelFileDescriptor getStatisticsStream(boolean updateAll);
 
     // Return true if we see the battery as currently charging.
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index c0d46f6..571ac1b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -37,34 +37,33 @@
     //TODO (163431584): need also consider other refresh rates.
     private static final long JANK_THRESHOLD_NANOS = 1000000000 / 60;
     private static final long UNKNOWN_TIMESTAMP = -1;
+    public static final int NANOS_IN_MILLISECOND = 1_000_000;
 
     private final HardwareRendererObserver mObserver;
+    private final int mTraceThresholdMissedFrames;
+    private final int mTraceThresholdFrameTimeMillis;
     private final ThreadedRendererWrapper mRendererWrapper;
     private final FrameMetricsWrapper mMetricsWrapper;
 
     private long mBeginTime = UNKNOWN_TIMESTAMP;
     private long mEndTime = UNKNOWN_TIMESTAMP;
-    private boolean mShouldTriggerTrace;
     private boolean mSessionEnd;
+    private boolean mCancelled = false;
     private int mTotalFramesCount = 0;
     private int mMissedFramesCount = 0;
     private long mMaxFrameTimeNanos = 0;
 
     private Session mSession;
 
-    /**
-     * Constructor of FrameTracker.
-     * @param session a trace session.
-     * @param handler a handler for handling callbacks.
-     * @param renderer a ThreadedRendererWrapper instance.
-     * @param metrics a FrameMetricsWrapper instance.
-     */
     public FrameTracker(@NonNull Session session, @NonNull Handler handler,
-            @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics) {
+            @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics,
+            int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) {
         mSession = session;
         mRendererWrapper = renderer;
         mMetricsWrapper = metrics;
         mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
+        mTraceThresholdMissedFrames = traceThresholdMissedFrames;
+        mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
     }
 
     /**
@@ -109,13 +108,15 @@
         }
         Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
         mRendererWrapper.removeObserver(mObserver);
-        mBeginTime = UNKNOWN_TIMESTAMP;
-        mEndTime = UNKNOWN_TIMESTAMP;
-        mShouldTriggerTrace = false;
+        mCancelled = true;
     }
 
     @Override
     public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+        if (mCancelled) {
+            return;
+        }
+
         // Since this callback might come a little bit late after the end() call.
         // We should keep tracking the begin / end timestamp.
         // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
@@ -136,13 +137,14 @@
             Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
                     mTotalFramesCount);
             Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
-                    (int) (mMaxFrameTimeNanos / 1_000_000));
+                    (int) (mMaxFrameTimeNanos / NANOS_IN_MILLISECOND));
 
             // Trigger perfetto if necessary.
-            if (mShouldTriggerTrace) {
-                if (DEBUG) {
-                    Log.v(TAG, "Found janky frame, triggering perfetto.");
-                }
+            boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
+                    && mMissedFramesCount >= mTraceThresholdMissedFrames;
+            boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
+                    && mMaxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
+            if (overMissedFramesThreshold || overFrameTimeThreshold) {
                 triggerPerfetto();
             }
             if (mSession.logToStatsd()) {
@@ -168,7 +170,6 @@
 
         if (isJankyFrame) {
             mMissedFramesCount += 1;
-            mShouldTriggerTrace = true;
         }
     }
 
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index fcaa963..771a72c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -36,7 +36,10 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.Build;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
@@ -47,6 +50,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -55,9 +59,21 @@
  */
 public class InteractionJankMonitor {
     private static final String TAG = InteractionJankMonitor.class.getSimpleName();
-    private static final boolean DEBUG = false;
     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
     private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
+    private static final String SETTINGS_ENABLED_KEY = "enabled";
+    private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
+    private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
+            "trace_threshold_missed_frames";
+    private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
+            "trace_threshold_frame_time_millis";
+    /** Default to being enabled on debug builds. */
+    private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
+    /** Default to collecting data for all CUJs. */
+    private static final int DEFAULT_SAMPLING_INTERVAL = 1;
+    /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
+    private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
+    private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
 
     // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
     public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
@@ -106,12 +122,20 @@
 
     private static volatile InteractionJankMonitor sInstance;
 
+    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+            this::updateProperties;
+
     private ThreadedRendererWrapper mRenderer;
     private FrameMetricsWrapper mMetrics;
     private SparseArray<FrameTracker> mRunningTrackers;
     private SparseArray<Runnable> mTimeoutActions;
     private HandlerThread mWorker;
+
     private boolean mInitialized;
+    private boolean mEnabled = DEFAULT_ENABLED;
+    private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
+    private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
+    private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
 
     /** @hide */
     @IntDef({
@@ -134,10 +158,12 @@
             CUJ_NOTIFICATION_APP_START,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface CujType {}
+    public @interface CujType {
+    }
 
     /**
      * Get the singleton of InteractionJankMonitor.
+     *
      * @return instance of InteractionJankMonitor
      */
     public static InteractionJankMonitor getInstance() {
@@ -154,6 +180,7 @@
 
     /**
      * This constructor should be only public to tests.
+     *
      * @param worker the worker thread for the callbacks
      */
     @VisibleForTesting
@@ -165,11 +192,11 @@
 
     /**
      * Init InteractionJankMonitor for later instrumentation.
+     *
      * @param view Any view in the view tree to get context and ThreadedRenderer.
      * @return boolean true if the instance has been initialized successfully.
      */
     public boolean init(@NonNull View view) {
-        //TODO (163505250): This should be no-op if not in droid food rom.
         if (!mInitialized) {
             synchronized (this) {
                 if (!mInitialized) {
@@ -180,7 +207,20 @@
                     mRenderer = new ThreadedRendererWrapper(view.getThreadedRenderer());
                     mMetrics = new FrameMetricsWrapper();
                     mWorker.start();
+                    mEnabled = DEFAULT_ENABLED;
+                    mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
                     mInitialized = true;
+
+                    // Post initialization to the background in case we're running on the main
+                    // thread.
+                    mWorker.getThreadHandler().post(
+                            () -> mPropertiesChangedListener.onPropertiesChanged(
+                                    DeviceConfig.getProperties(
+                                            DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+                    DeviceConfig.addOnPropertiesChangedListener(
+                            DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+                            new HandlerExecutor(mWorker.getThreadHandler()),
+                            mPropertiesChangedListener);
                 }
             }
         }
@@ -189,6 +229,7 @@
 
     /**
      * Create a {@link FrameTracker} instance.
+     *
      * @param session the session associates with this tracker
      * @return instance of the FrameTracker
      */
@@ -196,47 +237,50 @@
     public FrameTracker createFrameTracker(Session session) {
         synchronized (this) {
             if (!mInitialized) return null;
-            return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics);
+            return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics,
+                    mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis);
         }
     }
 
     /**
      * Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+     *
      * @param cujType the specific {@link InteractionJankMonitor.CujType}.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
     public boolean begin(@CujType int cujType) {
-        //TODO (163505250): This should be no-op if not in droid food rom.
         synchronized (this) {
-            return begin(cujType, 0L /* timeout */);
+            return begin(cujType, DEFAULT_TIMEOUT_MS);
         }
     }
 
     /**
      * Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+     *
      * @param cujType the specific {@link InteractionJankMonitor.CujType}.
      * @param timeout the elapsed time in ms until firing the timeout action.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
     public boolean begin(@CujType int cujType, long timeout) {
-        //TODO (163505250): This should be no-op if not in droid food rom.
         synchronized (this) {
             if (!mInitialized) {
                 Log.d(TAG, "Not initialized!", new Throwable());
                 return false;
             }
-            Session session = new Session(cujType);
-            FrameTracker tracker = getTracker(session);
+            boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+            if (!mEnabled || !shouldSample) {
+                return false;
+            }
+            FrameTracker tracker = getTracker(cujType);
             // Skip subsequent calls if we already have an ongoing tracing.
             if (tracker != null) return false;
 
             // begin a new trace session.
-            tracker = createFrameTracker(session);
+            tracker = createFrameTracker(new Session(cujType));
             mRunningTrackers.put(cujType, tracker);
             tracker.begin();
 
             // Cancel the trace if we don't get an end() call in specified duration.
-            timeout = timeout > 0L ? timeout : DEFAULT_TIMEOUT_MS;
             Runnable timeoutAction = () -> cancel(cujType);
             mTimeoutActions.put(cujType, timeoutAction);
             mWorker.getThreadHandler().postDelayed(timeoutAction, timeout);
@@ -246,6 +290,7 @@
 
     /**
      * End a trace session, must invoke {@link #init(View)} before invoking this method.
+     *
      * @param cujType the specific {@link InteractionJankMonitor.CujType}.
      * @return boolean true if the tracker is ended successfully, false otherwise.
      */
@@ -263,18 +308,18 @@
                 mTimeoutActions.remove(cujType);
             }
 
-            Session session = new Session(cujType);
-            FrameTracker tracker = getTracker(session);
+            FrameTracker tracker = getTracker(cujType);
             // Skip this call since we haven't started a trace yet.
             if (tracker == null) return false;
             tracker.end();
-            mRunningTrackers.remove(session.getCuj());
+            mRunningTrackers.remove(cujType);
             return true;
         }
     }
 
     /**
      * Cancel the trace session, must invoke {@link #init(View)} before invoking this method.
+     *
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
     public boolean cancel(@CujType int cujType) {
@@ -291,50 +336,40 @@
                 mTimeoutActions.remove(cujType);
             }
 
-            Session session = new Session(cujType);
-            FrameTracker tracker = getTracker(session);
+            FrameTracker tracker = getTracker(cujType);
             // Skip this call since we haven't started a trace yet.
             if (tracker == null) return false;
             tracker.cancel();
-            mRunningTrackers.remove(session.getCuj());
+            mRunningTrackers.remove(cujType);
             return true;
         }
     }
 
-    private void destroy() {
-        synchronized (this) {
-            int trackers = mRunningTrackers.size();
-            for (int i = 0; i < trackers; i++) {
-                mRunningTrackers.valueAt(i).cancel();
-            }
-            mRunningTrackers = null;
-            mTimeoutActions.clear();
-            mTimeoutActions = null;
-            mWorker.quit();
-            mWorker = null;
-        }
-    }
-
-    /**
-     * Abandon current instance.
-     */
-    @VisibleForTesting
-    public static void abandon() {
-        if (sInstance == null) return;
-        synchronized (InteractionJankMonitor.class) {
-            if (sInstance == null) return;
-            sInstance.destroy();
-            sInstance = null;
-        }
-    }
-
-    private FrameTracker getTracker(Session session) {
+    private FrameTracker getTracker(@CujType int cuj) {
         synchronized (this) {
             if (!mInitialized) return null;
-            return mRunningTrackers.get(session.getCuj());
+            return mRunningTrackers.get(cuj);
         }
     }
 
+    private void updateProperties(DeviceConfig.Properties properties) {
+        synchronized (this) {
+            mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+                    DEFAULT_SAMPLING_INTERVAL);
+            mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+            mTraceThresholdMissedFrames = properties.getInt(SETTINGS_THRESHOLD_MISSED_FRAMES_KEY,
+                    DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+            mTraceThresholdFrameTimeMillis = properties.getInt(
+                    SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
+                    DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+        }
+    }
+
+    @VisibleForTesting
+    public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
+        return mPropertiesChangedListener;
+    }
+
     /**
      * Trigger the perfetto daemon to collect and upload data.
      */
@@ -402,12 +437,14 @@
      * A class to represent a session.
      */
     public static class Session {
-        private @CujType int mCujType;
+        @CujType
+        private int mCujType;
 
         public Session(@CujType int cujType) {
             mCujType = cujType;
         }
 
+        @CujType
         public int getCuj() {
             return mCujType;
         }
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 41be5c4..2237efc 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -57,6 +57,7 @@
     public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
     public static String SYSTEM_CHANGES = "SYSTEM_CHANGES";
     public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
+    public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
 
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -191,6 +192,13 @@
                 NotificationManager.IMPORTANCE_LOW);
         channelsList.add(dndChanges);
 
+        final NotificationChannel newFeaturePrompt = new NotificationChannel(
+                ACCESSIBILITY_MAGNIFICATION,
+                context.getString(R.string.notification_channel_accessibility_magnification),
+                NotificationManager.IMPORTANCE_HIGH);
+        newFeaturePrompt.setBlockable(true);
+        channelsList.add(newFeaturePrompt);
+
         nm.createNotificationChannels(channelsList);
     }
 
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c110b26..c85b5d7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -18,9 +18,10 @@
 package com.android.internal.os;
 
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.BasicShellCommandHandler;
 import android.os.Build;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintStream;
 
 public abstract class BaseCommand {
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 2676745..9e59e50 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -203,7 +203,7 @@
             }
         }
         return getStats(IBatteryStats.Stub.asInterface(
-                ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+                ServiceManager.getService(BatteryStats.SERVICE_NAME)), true);
     }
 
     @UnsupportedAppUsage
@@ -223,8 +223,13 @@
 
     @UnsupportedAppUsage
     public BatteryStats getStats() {
+        return getStats(true /* updateAll */);
+    }
+
+    /** Retrieves stats from BatteryService, optionally getting updated numbers */
+    public BatteryStats getStats(boolean updateAll) {
         if (mStats == null) {
-            load();
+            load(updateAll);
         }
         return mStats;
     }
@@ -720,19 +725,23 @@
 
     @UnsupportedAppUsage
     private void load() {
+        load(true);
+    }
+
+    private void load(boolean updateAll) {
         if (mBatteryInfo == null) {
             return;
         }
-        mStats = getStats(mBatteryInfo);
+        mStats = getStats(mBatteryInfo, updateAll);
         if (mCollectBatteryBroadcast) {
             mBatteryBroadcast = mContext.registerReceiver(null,
                     new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         }
     }
 
-    private static BatteryStatsImpl getStats(IBatteryStats service) {
+    private static BatteryStatsImpl getStats(IBatteryStats service, boolean updateAll) {
         try {
-            ParcelFileDescriptor pfd = service.getStatisticsStream();
+            ParcelFileDescriptor pfd = service.getStatisticsStream(updateAll);
             if (pfd != null) {
                 if (false) {
                     Log.d(TAG, "selinux context: "
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 854fb17..44dca9b 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -69,6 +69,6 @@
     void onRegistrationFailed(in CellIdentity cellIdentity,
              String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
     void onBarringInfoChanged(in BarringInfo barringInfo);
-    void onPhysicalChannelConfigChanged(in List<PhysicalChannelConfig> configs);
-    void onDataEnabledChanged(boolean enabled, int reason);
+    void onPhysicalChannelConfigurationChanged(in List<PhysicalChannelConfig> configs);
+
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index ae1657b..a28a663 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -41,10 +41,16 @@
             IOnSubscriptionsChangedListener callback);
     void removeOnSubscriptionsChangedListener(String pkg,
             IOnSubscriptionsChangedListener callback);
-
-    void listenWithEventList(in int subId, String pkg, String featureId,
-            IPhoneStateListener callback, in int[] events, boolean notifyNow);
-
+    /**
+      * @deprecated Use {@link #listenWithFeature(String, String, IPhoneStateListener, int,
+      * boolean) instead
+      */
+    @UnsupportedAppUsage
+    void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
+    void listenWithFeature(String pkg, String featureId, IPhoneStateListener callback, long events,
+            boolean notifyNow);
+    void listenForSubscriber(in int subId, String pkg, String featureId,
+            IPhoneStateListener callback, long events, boolean notifyNow);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void notifyCallStateForAllSubs(int state, String incomingNumber);
     void notifyCallState(in int phoneId, in int subId, int state, String incomingNumber);
@@ -93,6 +99,6 @@
     void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
             String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
     void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
-    void notifyPhysicalChannelConfigForSubscriber(in int subId,
+    void notifyPhysicalChannelConfigurationForSubscriber(in int subId,
             in List<PhysicalChannelConfig> configs);
 }
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index a87e8aa..8012540 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -15,15 +15,11 @@
 package com.android.internal.util;
 
 import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.util.EventLog;
-import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.SparseLongArray;
 
@@ -135,8 +131,16 @@
         mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
 
         // Post initialization to the background in case we're running on the main thread.
-        BackgroundThread.getHandler().post(this::registerSettingsObserver);
-        BackgroundThread.getHandler().post(this::readSettings);
+        BackgroundThread.getHandler().post(() -> this.updateProperties(
+                DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+                BackgroundThread.getExecutor(), this::updateProperties);
+    }
+
+    private void updateProperties(DeviceConfig.Properties properties) {
+        mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+                DEFAULT_SAMPLING_INTERVAL);
+        mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
     }
 
     /**
@@ -171,28 +175,6 @@
         }
     }
 
-    private void registerSettingsObserver() {
-        Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LATENCY_TRACKER);
-        mContext.getContentResolver().registerContentObserver(
-                settingsUri, false, new SettingsObserver(this), UserHandle.myUserId());
-    }
-
-    private void readSettings() {
-        KeyValueListParser parser = new KeyValueListParser(',');
-        String settingsValue = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.LATENCY_TRACKER);
-
-        try {
-            parser.setString(settingsValue);
-            mSamplingInterval = parser.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
-                    DEFAULT_SAMPLING_INTERVAL);
-            mEnabled = parser.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Incorrect settings format", e);
-            mEnabled = false;
-        }
-    }
-
     public static boolean isEnabled(Context ctx) {
         return getInstance(ctx).isEnabled();
     }
@@ -236,8 +218,8 @@
     /**
      * Logs an action that has started and ended. This needs to be called from the main thread.
      *
-     * @param action          The action to end. One of the ACTION_* values.
-     * @param duration        The duration of the action in ms.
+     * @param action   The action to end. One of the ACTION_* values.
+     * @param duration The duration of the action in ms.
      */
     public void logAction(int action, int duration) {
         boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
@@ -260,18 +242,4 @@
                     FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
         }
     }
-
-    private static class SettingsObserver extends ContentObserver {
-        private final LatencyTracker mThisTracker;
-
-        SettingsObserver(LatencyTracker thisTracker) {
-            super(BackgroundThread.getHandler());
-            mThisTracker = thisTracker;
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            mThisTracker.readSettings();
-        }
-    }
 }
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 1ea918a..1c78750 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -50,6 +50,7 @@
 
     jmethodID dispatchInputEvent;
     jmethodID onFocusEvent;
+    jmethodID onPointerCaptureEvent;
     jmethodID onBatchedInputEventPending;
 } gInputEventReceiverClassInfo;
 
@@ -345,6 +346,19 @@
                 finishInputEvent(seq, true /* handled */);
                 continue;
             }
+            case AINPUT_EVENT_TYPE_CAPTURE: {
+                const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(inputEvent);
+                if (kDebugDispatchCycle) {
+                    ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s",
+                          getInputChannelName().c_str(),
+                          toString(captureEvent->getPointerCaptureEnabled()));
+                }
+                env->CallVoidMethod(receiverObj.get(),
+                                    gInputEventReceiverClassInfo.onPointerCaptureEvent,
+                                    jboolean(captureEvent->getPointerCaptureEnabled()));
+                finishInputEvent(seq, true /* handled */);
+                continue;
+            }
 
             default:
                 assert(false); // InputConsumer should prevent this from ever happening
@@ -489,6 +503,9 @@
             "dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
     gInputEventReceiverClassInfo.onFocusEvent =
             GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
+    gInputEventReceiverClassInfo.onPointerCaptureEvent =
+            GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onPointerCaptureEvent",
+                             "(Z)V");
     gInputEventReceiverClassInfo.onBatchedInputEventPending =
             GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onBatchedInputEventPending",
                              "(I)V");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 6ffafc1..3156f71 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -78,7 +78,6 @@
 #include <android-base/unique_fd.h>
 #include <bionic/malloc.h>
 #include <bionic/mte.h>
-#include <bionic/mte_kernel.h>
 #include <cutils/fs.h>
 #include <cutils/memory.h>
 #include <cutils/multiuser.h>
@@ -1650,28 +1649,6 @@
   }
 }
 
-#ifdef ANDROID_EXPERIMENTAL_MTE
-static void SetTagCheckingLevel(int level) {
-#ifdef __aarch64__
-  if (!(getauxval(AT_HWCAP2) & HWCAP2_MTE)) {
-    return;
-  }
-
-  int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
-  if (tagged_addr_ctrl < 0) {
-    ALOGE("prctl(PR_GET_TAGGED_ADDR_CTRL) failed: %s", strerror(errno));
-    return;
-  }
-
-  tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | level;
-  if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
-    ALOGE("prctl(PR_SET_TAGGED_ADDR_CTRL, %d) failed: %s", tagged_addr_ctrl,
-          strerror(errno));
-  }
-#endif
-}
-#endif
-
 // Utility routine to specialize a zygote child process.
 static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
                              jint runtime_flags, jobjectArray rlimits,
@@ -1807,22 +1784,14 @@
       heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
       break;
     case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC:
-#ifdef ANDROID_EXPERIMENTAL_MTE
-      SetTagCheckingLevel(PR_MTE_TCF_ASYNC);
-#endif
       heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
       break;
     case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC:
-#ifdef ANDROID_EXPERIMENTAL_MTE
-      SetTagCheckingLevel(PR_MTE_TCF_SYNC);
-#endif
       heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
       break;
     default:
-#ifdef ANDROID_EXPERIMENTAL_MTE
-      SetTagCheckingLevel(PR_MTE_TCF_NONE);
-#endif
       heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+      break;
   }
   android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
   // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
diff --git a/core/proto/android/stats/camera/Android.bp b/core/proto/android/stats/camera/Android.bp
new file mode 100644
index 0000000..cc75e57
--- /dev/null
+++ b/core/proto/android/stats/camera/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "cameraprotosnano",
+    proto: {
+        type: "nano",
+    },
+    srcs: [
+        "*.proto",
+    ],
+    java_version: "1.8",
+    target: {
+        android: {
+            jarjar_rules: "jarjar-rules.txt",
+        },
+        host: {
+            static_libs: ["libprotobuf-java-nano"],
+        }
+    },
+    sdk_version: "core_platform",
+}
diff --git a/core/proto/android/stats/camera/camera.proto b/core/proto/android/stats/camera/camera.proto
new file mode 100644
index 0000000..4062855
--- /dev/null
+++ b/core/proto/android/stats/camera/camera.proto
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.stats.camera;
+option java_multiple_files = true;
+
+message CameraStreamProto {
+    // The stream width (in pixels)
+    optional int32 width = 1;
+    // The stream height (in pixels)
+    optional int32 height = 2;
+    // The format of the stream
+    optional int32 format = 3;
+    // The dataspace of the stream
+    optional int32 data_space = 4;
+    // The usage flag of the stream
+    optional int64 usage = 5;
+
+    // The number of requests for this stream
+    optional int64 request_count = 6;
+    // The number of buffer error for this stream
+    optional int64 error_count = 7;
+    // The capture latency of first request for this stream
+    optional int32 first_capture_latency_millis = 8;
+
+    // The maximum number of hal buffers
+    optional int32 max_hal_buffers = 9;
+    // The maximum number of app buffers
+    optional int32 max_app_buffers = 10;
+}
diff --git a/core/proto/android/stats/camera/jarjar-rules.txt b/core/proto/android/stats/camera/jarjar-rules.txt
new file mode 100644
index 0000000..40043a86
--- /dev/null
+++ b/core/proto/android/stats/camera/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/core/proto/android/stats/tv/tif_enums.proto b/core/proto/android/stats/tv/tif_enums.proto
new file mode 100644
index 0000000..a9028e5
--- /dev/null
+++ b/core/proto/android/stats/tv/tif_enums.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.stats.tv;
+option java_multiple_files = true;
+
+// Enums for TV Input Framework
+option java_outer_classname = "TifStatsEnums";
+
+// Tune State of a TV Input Service Framework
+enum TifTuneState {
+  TIF_TUNE_STATE_UNKNOWN = 0;
+  CREATED = 1;
+  SURFACE_ATTACHED = 2;
+  SURFACE_DETACHED = 3;
+  RELEASED = 4;
+  TUNE_STARTED = 5;
+  VIDEO_AVAILABLE = 6;
+
+  // Keep in sync with TvInputManager
+  // Use the TvInputManager value + 100
+  VIDEO_UNAVAILABLE_REASON_UNKNOWN = 100;
+  VIDEO_UNAVAILABLE_REASON_TUNING = 101;
+  VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 102;
+  VIDEO_UNAVAILABLE_REASON_BUFFERING = 103;
+  VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 104;
+  VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 105;
+  VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 106;
+  VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION=107;
+  VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED=108;
+  VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE=109;
+  VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED=110;
+  VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION=111;
+  VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING=112;
+  VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD=113;
+  VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE=114;
+  VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID=115;
+  VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT=116;
+  VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING=117;
+  VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN=118;
+}
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 552a1bd..9662b8e 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -28,7 +28,6 @@
         android:layout_height="wrap_content"
         android:gravity="end"
         android:orientation="horizontal"
-        android:paddingEnd="@dimen/bubble_gone_padding_end"
         android:background="@color/notification_action_list_background_color"
         >
 
@@ -45,22 +44,34 @@
             <!-- actions will be added here -->
         </com.android.internal.widget.NotificationActionListLayout>
 
-        <ImageView
-            android:id="@+id/snooze_button"
-            android:layout_width="@dimen/notification_actions_icon_size"
-            android:layout_height="@dimen/notification_actions_icon_size"
-            android:layout_gravity="center_vertical|end"
-            android:visibility="gone"
-            android:scaleType="centerInside"
-            />
+        <!--
+        This nested linear layout exists to ensure that if the neither of the contained
+        actions is visible we have some minimum padding at the end of the actions is present,
+        then there will be 12dp of padding at the end of the actions list.
+        -->
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:minWidth="@dimen/snooze_and_bubble_gone_padding_end"
+            >
+            <ImageView
+                android:id="@+id/snooze_button"
+                android:layout_width="@dimen/notification_actions_icon_size"
+                android:layout_height="@dimen/notification_actions_icon_size"
+                android:layout_gravity="center_vertical|end"
+                android:visibility="gone"
+                android:scaleType="centerInside"
+                />
 
-        <ImageView
-            android:id="@+id/bubble_button"
-            android:layout_width="@dimen/notification_actions_icon_size"
-            android:layout_height="@dimen/notification_actions_icon_size"
-            android:layout_gravity="center_vertical|end"
-            android:visibility="gone"
-            android:scaleType="centerInside"
-            />
+            <ImageView
+                android:id="@+id/bubble_button"
+                android:layout_width="@dimen/notification_actions_icon_size"
+                android:layout_height="@dimen/notification_actions_icon_size"
+                android:layout_gravity="center_vertical|end"
+                android:visibility="gone"
+                android:scaleType="centerInside"
+                />
+        </LinearLayout>
     </LinearLayout>
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a26473a..b0ee12a 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -50,20 +50,18 @@
         android:theme="@style/Theme.DeviceDefault.Notification"
         >
 
-        <TextView
-            android:id="@+id/app_name_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="@dimen/notification_header_separating_margin"
-            android:singleLine="true"
-            android:textAppearance="?attr/notificationHeaderTextAppearance"
-            android:visibility="?attr/notificationHeaderAppNameVisibility"
-            />
-
         <include layout="@layout/notification_top_line_views" />
 
     </NotificationTopLineView>
 
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        />
+
     <com.android.internal.widget.NotificationExpandButton
         android:id="@+id/expand_button"
         android:layout_width="@dimen/notification_header_expand_icon_size"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index ded16b7..69d4a12 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -68,6 +68,12 @@
             android:theme="@style/Theme.DeviceDefault.Notification"
             >
 
+            <!--
+            NOTE: The notification_top_line_views layout contains the app_name_text.
+            In order to include the title view at the beginning, the Notification.Builder
+            has logic to hide that view whenever this title view is to be visible.
+            -->
+
             <TextView
                 android:id="@+id/title"
                 android:layout_width="wrap_content"
@@ -138,6 +144,14 @@
         />
 
     <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        />
+
+    <FrameLayout
         android:id="@+id/expand_button_touch_container"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 520ae28..f9364d5 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -327,10 +327,10 @@
             android:id="@+id/expand_button_touch_container"
             android:layout_width="wrap_content"
             android:layout_height="@dimen/conversation_expand_button_size"
-            android:paddingStart="16dp"
+            android:paddingStart="@dimen/conversation_expand_button_side_margin"
             android:orientation="horizontal"
             android:layout_gravity="end|top"
-            android:paddingEnd="@dimen/notification_content_margin_end"
+            android:paddingEnd="@dimen/conversation_expand_button_side_margin"
             android:clipToPadding="false"
             android:clipChildren="false"
             >
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 51974ac..c71e886 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -14,13 +14,23 @@
   ~ limitations under the License
   -->
 <!--
- This layout file should be included inside a NotificationTopLineView, usually after either a
- <TextView android:id="@+id/app_name_text"/> or <TextView android:id="@+id/title"/>
+ This layout file should be included inside a NotificationTopLineView, sometimes after a
+ <TextView android:id="@+id/title"/>
 -->
 <merge
     xmlns:android="http://schemas.android.com/apk/res/android">
 
     <TextView
+        android:id="@+id/app_name_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:singleLine="true"
+        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:visibility="?attr/notificationHeaderAppNameVisibility"
+        />
+
+    <TextView
         android:id="@+id/header_text_secondary_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index fc9f670..c32e8dc 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2834,6 +2834,38 @@
         <attr name="resource" format="reference" />
     </declare-styleable>
 
+    <!-- The <code>property</code> tag is used to attach additional data that can
+         be supplied to the parent component. A component element can contain any
+         number of <code>property</code> subelements. Valid names are any of the
+         <code>PROPERTY_</code> constants defined in the
+         {@link android.content.pm.PackageManager PackageManager} class. Values
+         are obtained using the appropriate method on the
+         {@link android.content.pm.PackageManager.Property PackageManager.Property} class.
+         <p>Ordinary values are specified through the value attribute. Resource IDs are
+         specified through the resource attribute.
+         <p>It is invalid to specify both a value and resource attributes. -->
+    <declare-styleable name="AndroidManifestProperty"
+         parent="AndroidManifestApplication
+                 AndroidManifestActivity
+                 AndroidManifestReceiver
+                 AndroidManifestProvider
+                 AndroidManifestService">
+        <attr name="name" />
+        <!-- Concrete value to assign to this property.
+             The data can later be retrieved from the property object
+             through
+             {@link android.content.pm.PackageManager.Property#getString Property.getString},
+             {@link android.content.pm.PackageManager.Property#getInteger Property.getInteger},
+             {@link android.content.pm.PackageManager.Property#getBoolean Property.getBoolean},
+             or {@link android.content.pm.PackageManager.Property#getFloat Property.getFloat}
+             depending on the type used here. -->
+        <attr name="value" />
+        <!-- The resource identifier to assign to this property.
+             The resource identifier can later be retrieved from the property object through
+             {@link android.content.pm.PackageManager.Property#getResourceId Property.getResourceId}. -->
+        <attr name="resource" />
+    </declare-styleable>
+
     <!-- The <code>intent-filter</code> tag is used to construct an
          {@link android.content.IntentFilter} object that will be used
          to determine which component can handle a particular
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 79eae67..19591f6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -259,6 +259,9 @@
     <!-- The height of the background for a notification header on a group -->
     <dimen name="notification_header_background_height">49.5dp</dimen>
 
+    <!-- The height of the full-width touch rectangle for the notification header -->
+    <dimen name="notification_header_touchable_height">36dp</dimen>
+
     <!-- The top padding for the notification header -->
     <dimen name="notification_header_padding_top">16dp</dimen>
 
@@ -340,11 +343,8 @@
     <!-- The margin of the content to an image-->
     <dimen name="notification_content_image_margin_end">8dp</dimen>
 
-    <!-- The padding at the end of actions when the bubble button is visible-->
-    <dimen name="bubble_visible_padding_end">3dp</dimen>
-
-    <!-- The padding at the end of actions when the bubble button is gone-->
-    <dimen name="bubble_gone_padding_end">12dp</dimen>
+    <!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
+    <dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
 
     <!-- The spacing between messages in Notification.MessagingStyle -->
     <dimen name="notification_messaging_spacing">6dp</dimen>
@@ -744,6 +744,9 @@
     <dimen name="conversation_expand_button_size">80dp</dimen>
     <!-- Top margin of the expand button for conversations when expanded -->
     <dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen>
+    <!-- Side margin of the expand button for conversations.
+         width of expand asset (22) + 2 * this (13) == notification_header_expand_icon_size (48) -->
+    <dimen name="conversation_expand_button_side_margin">13dp</dimen>
     <!-- Side margins of the conversation badge in relation to the conversation icon -->
     <dimen name="conversation_badge_side_margin">36dp</dimen>
     <!-- size of the notification badge when applied to the conversation icon -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8eb0853..be6b6b1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -717,6 +717,10 @@
         [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6665375982962336520] -->
     <string name="notification_channel_foreground_service">Apps consuming battery</string>
 
+    <!-- Text shown when viewing channel settings for notifications related to accessibility
+         magnification. [CHAR_LIMIT=NONE]-->
+    <string name="notification_channel_accessibility_magnification">Magnification</string>
+
     <!-- Label for foreground service notification when one app is running.
     [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
     <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -5767,4 +5771,16 @@
     <string name="config_pdp_reject_service_not_subscribed"></string>
     <!-- pdp data reject dialog string for cause 55 (MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED) [CHAR LIMIT=100] -->
     <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed"></string>
+
+    <!-- Window magnification prompt related string. -->
+
+    <!-- Notification title to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
+    <string name="window_magnification_prompt_title">New: Window Magnifier</string>
+    <!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
+    <string name="window_magnification_prompt_content">You can now magnify some or all of your screen</string>
+    <!-- Notification action to bring the user to magnification settings page. [CHAR LIMIT=50] -->
+    <string name="turn_on_magnification_settings_action">Turn on in Settings</string>
+    <!-- Notification action to dismiss. [CHAR LIMIT=50] -->
+    <string name="dismiss_action">Dismiss</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 23733af..40aae9e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2861,6 +2861,7 @@
   <java-symbol type="id" name="header_text" />
   <java-symbol type="id" name="header_text_secondary" />
   <java-symbol type="id" name="expand_button" />
+  <java-symbol type="id" name="alternate_expand_target" />
   <java-symbol type="id" name="notification_header" />
   <java-symbol type="id" name="notification_top_line" />
   <java-symbol type="id" name="time_divider" />
@@ -2878,6 +2879,7 @@
   <java-symbol type="dimen" name="notification_content_margin_top" />
   <java-symbol type="dimen" name="notification_content_margin" />
   <java-symbol type="dimen" name="notification_header_background_height" />
+  <java-symbol type="dimen" name="notification_header_touchable_height" />
   <java-symbol type="dimen" name="notification_header_expand_icon_size" />
   <java-symbol type="dimen" name="notification_expand_button_padding_top" />
   <java-symbol type="dimen" name="notification_header_icon_size" />
@@ -3458,6 +3460,7 @@
   <java-symbol type="string" name="notification_channel_heavy_weight_app" />
   <java-symbol type="string" name="notification_channel_system_changes" />
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
+  <java-symbol type="string" name="notification_channel_accessibility_magnification" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
@@ -3559,8 +3562,6 @@
   <java-symbol type="id" name="clip_children_tag" />
   <java-symbol type="id" name="bubble_button" />
   <java-symbol type="id" name="snooze_button" />
-  <java-symbol type="dimen" name="bubble_visible_padding_end" />
-  <java-symbol type="dimen" name="bubble_gone_padding_end" />
   <java-symbol type="dimen" name="text_size_body_2_material" />
   <java-symbol type="dimen" name="messaging_avatar_size" />
   <java-symbol type="dimen" name="messaging_group_sending_progress_size" />
@@ -4096,4 +4097,10 @@
   <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
+
+  <!-- Window magnification prompt -->
+  <java-symbol type="string" name="window_magnification_prompt_title" />
+  <java-symbol type="string" name="window_magnification_prompt_content" />
+  <java-symbol type="string" name="turn_on_magnification_settings_action" />
+  <java-symbol type="string" name="dismiss_action" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
index 2f2bef8..119b70a 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
@@ -24,16 +24,18 @@
 
     @Test
     public void testBuildEmailAndGetValue() {
-        AppSearchEmail email = new AppSearchEmail.Builder("uri")
-                .setFrom("FakeFromAddress")
-                .setCc("CC1", "CC2")
-                // Score and Property are mixed into the middle to make sure DocumentBuilder's
-                // methods can be interleaved with EmailBuilder's methods.
-                .setScore(1)
-                .setPropertyString("propertyKey", "propertyValue1", "propertyValue2")
-                .setSubject("subject")
-                .setBody("EmailBody")
-                .build();
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri")
+                        .setFrom("FakeFromAddress")
+                        .setCc("CC1", "CC2")
+                        // Score and Property are mixed into the middle to make sure
+                        // DocumentBuilder's
+                        // methods can be interleaved with EmailBuilder's methods.
+                        .setScore(1)
+                        .setPropertyString("propertyKey", "propertyValue1", "propertyValue2")
+                        .setSubject("subject")
+                        .setBody("EmailBody")
+                        .build();
 
         assertThat(email.getUri()).isEqualTo("uri");
         assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
@@ -42,8 +44,9 @@
         assertThat(email.getBcc()).isNull();
         assertThat(email.getScore()).isEqualTo(1);
         assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1");
-        assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly(
-                "propertyValue1", "propertyValue2");
+        assertThat(email.getPropertyStringArray("propertyKey"))
+                .asList()
+                .containsExactly("propertyValue1", "propertyValue2");
         assertThat(email.getSubject()).isEqualTo("subject");
         assertThat(email.getBody()).isEqualTo("EmailBody");
     }
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
deleted file mode 100644
index c171270..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 static org.testng.Assert.expectThrows;
-
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.app.appsearch.exceptions.IllegalSchemaException;
-
-import org.junit.Test;
-
-public class AppSearchSchemaTest {
-    @Test
-    public void testInvalidEnums() {
-        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
-        expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
-        expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
-    }
-
-    @Test
-    public void testMissingFields() {
-        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
-        IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
-        assertThat(e).hasMessageThat().contains("Missing field: dataType");
-
-        builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
-        e = expectThrows(IllegalSchemaException.class, builder::build);
-        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
-
-        builder.setSchemaType("TestType");
-        e = expectThrows(IllegalSchemaException.class, builder::build);
-        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
-
-        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
-        builder.build();
-    }
-
-    @Test
-    public void testDuplicateProperties() {
-        AppSearchSchema.Builder builder = new AppSearchSchema.Builder("Email")
-                .addProperty(new PropertyConfig.Builder("subject")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                );
-        IllegalSchemaException e = expectThrows(IllegalSchemaException.class,
-                () -> builder.addProperty(new PropertyConfig.Builder("subject")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()));
-        assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
-    }
-}
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
deleted file mode 100644
index 9c29943..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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 static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-
-public class GenericDocumentTest {
-    private static final byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
-    private static final byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6, (byte) 7};
-    private static final GenericDocument sDocumentProperties1 = new GenericDocument
-            .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
-            .setCreationTimestampMillis(12345L)
-            .build();
-    private static final GenericDocument sDocumentProperties2 = new GenericDocument
-            .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
-            .setCreationTimestampMillis(6789L)
-            .build();
-
-    @Test
-    public void testDocumentEquals_Identical() {
-        GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setTtlMillis(1L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .build();
-        GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setTtlMillis(1L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .build();
-        assertThat(document1).isEqualTo(document2);
-        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
-    }
-
-    @Test
-    public void testDocumentEquals_DifferentOrder() {
-        GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .build();
-
-        // Create second document with same parameter but different order.
-        GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .build();
-        assertThat(document1).isEqualTo(document2);
-        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
-    }
-
-    @Test
-    public void testDocumentEquals_Failure() {
-        GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .build();
-
-        // Create second document with same order but different value.
-        GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
-                .build();
-        assertThat(document1).isNotEqualTo(document2);
-        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
-    }
-
-    @Test
-    public void testDocumentEquals_Failure_RepeatedFieldOrder() {
-        GenericDocument document1 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .build();
-
-        // Create second document with same order but different value.
-        GenericDocument document2 = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyBoolean("booleanKey1", true, true, false) // Different
-                .build();
-        assertThat(document1).isNotEqualTo(document2);
-        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
-    }
-
-    @Test
-    public void testDocumentGetSingleValue() {
-        GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setScore(1)
-                .setTtlMillis(1L)
-                .setPropertyLong("longKey1", 1L)
-                .setPropertyDouble("doubleKey1", 1.0)
-                .setPropertyBoolean("booleanKey1", true)
-                .setPropertyString("stringKey1", "test-value1")
-                .setPropertyBytes("byteKey1", sByteArray1)
-                .setPropertyDocument("documentKey1", sDocumentProperties1)
-                .build();
-        assertThat(document.getUri()).isEqualTo("uri1");
-        assertThat(document.getTtlMillis()).isEqualTo(1L);
-        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
-        assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
-        assertThat(document.getScore()).isEqualTo(1);
-        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
-        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
-        assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
-        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
-        assertThat(document.getPropertyBytes("byteKey1"))
-                .asList().containsExactly((byte) 1, (byte) 2, (byte) 3);
-        assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
-    }
-
-    @Test
-    public void testDocumentGetArrayValues() {
-        GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .build();
-
-        assertThat(document.getUri()).isEqualTo("uri1");
-        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
-        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
-        assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
-                .containsExactly(1.0, 2.0, 3.0);
-        assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
-                .containsExactly(true, false, true);
-        assertThat(document.getPropertyStringArray("stringKey1")).asList()
-                .containsExactly("test-value1", "test-value2", "test-value3");
-        assertThat(document.getPropertyBytesArray("byteKey1")).asList()
-                .containsExactly(sByteArray1, sByteArray2);
-        assertThat(document.getPropertyDocumentArray("documentKey1")).asList()
-                .containsExactly(sDocumentProperties1, sDocumentProperties2);
-    }
-
-    @Test
-    public void testDocument_ToString() throws Exception {
-        GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
-                .setCreationTimestampMillis(5L)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "String1", "String2", "String3")
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .build();
-        String exceptedString = "{ key: 'creationTimestampMillis' value: 5 } "
-                + "{ key: 'namespace' value:  } "
-                + "{ key: 'properties' value: "
-                +       "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
-                +       "{ key: 'byteKey1' value: "
-                +             "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
-                +             "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] }  } "
-                +       "{ key: 'documentKey1' value: [ '"
-                +             "{ key: 'creationTimestampMillis' value: 12345 } "
-                +             "{ key: 'namespace' value:  } "
-                +             "{ key: 'properties' value:  } "
-                +             "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
-                +             "{ key: 'score' value: 0 } "
-                +             "{ key: 'ttlMillis' value: 0 } "
-                +             "{ key: 'uri' value: sDocumentProperties1 } ' '"
-                +             "{ key: 'creationTimestampMillis' value: 6789 } "
-                +             "{ key: 'namespace' value:  } "
-                +             "{ key: 'properties' value:  } "
-                +             "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
-                +             "{ key: 'score' value: 0 } "
-                +             "{ key: 'ttlMillis' value: 0 } "
-                +             "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
-                +       "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
-                +       "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
-                +       "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] }  } "
-                + "{ key: 'schemaType' value: schemaType1 } "
-                + "{ key: 'score' value: 0 } "
-                + "{ key: 'ttlMillis' value: 0 } "
-                + "{ key: 'uri' value: uri1 } ";
-        assertThat(document.toString()).isEqualTo(exceptedString);
-    }
-
-    @Test
-    public void testDocumentGetValues_DifferentTypes() {
-        GenericDocument document = new GenericDocument.Builder("uri1", "schemaType1")
-                .setScore(1)
-                .setPropertyLong("longKey1", 1L)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .build();
-
-        // Get a value for a key that doesn't exist
-        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
-        assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
-
-        // Get a value with a single element as an array and as a single value
-        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
-        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
-
-        // Get a value with multiple elements as an array and as a single value
-        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
-        assertThat(document.getPropertyStringArray("stringKey1")).asList()
-                .containsExactly("test-value1", "test-value2", "test-value3");
-
-        // Get a value of the wrong type
-        assertThat(document.getPropertyDouble("longKey1")).isEqualTo(0.0);
-        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
-    }
-
-    @Test
-    public void testDocumentInvalid() {
-        GenericDocument.Builder builder = new GenericDocument.Builder("uri1", "schemaType1");
-        expectThrows(
-                IllegalArgumentException.class,
-                () -> builder.setPropertyBoolean("test", new boolean[]{}));
-    }
-}
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 d4635fd..9fd480d 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
@@ -18,69 +18,33 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.testng.Assert.expectThrows;
-
 import android.os.Bundle;
 
 import org.junit.Test;
 
 public class SearchSpecTest {
     @Test
-    public void buildSearchSpecWithoutTermMatchType() {
-        expectThrows(RuntimeException.class, () -> new SearchSpec.Builder()
-                .addSchema("testSchemaType")
-                .build());
-    }
-
-    @Test
-    public void testBuildSearchSpec() {
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
-                .addNamespace("namespace1", "namespace2")
-                .addSchema("schemaTypes1", "schemaTypes2")
-                .setSnippetCount(5)
-                .setSnippetCountPerProperty(10)
-                .setMaxSnippetSize(15)
-                .setNumPerPage(42)
-                .setOrder(SearchSpec.ORDER_ASCENDING)
-                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
-                .build();
-
-        assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
-        assertThat(searchSpec.getNamespaces())
-                .containsExactly("namespace1", "namespace2").inOrder();
-        assertThat(searchSpec.getSchemas())
-                .containsExactly("schemaTypes1", "schemaTypes2").inOrder();
-        assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
-        assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
-        assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
-        assertThat(searchSpec.getNumPerPage()).isEqualTo(42);
-        assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
-        assertThat(searchSpec.getRankingStrategy())
-                .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
-    }
-
-    @Test
     public void testGetBundle() {
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
-                .addNamespace("namespace1", "namespace2")
-                .addSchema("schemaTypes1", "schemaTypes2")
-                .setSnippetCount(5)
-                .setSnippetCountPerProperty(10)
-                .setMaxSnippetSize(15)
-                .setNumPerPage(42)
-                .setOrder(SearchSpec.ORDER_ASCENDING)
-                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
-                .build();
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .addNamespace("namespace1", "namespace2")
+                        .addSchemaType("schemaTypes1", "schemaTypes2")
+                        .setSnippetCount(5)
+                        .setSnippetCountPerProperty(10)
+                        .setMaxSnippetSize(15)
+                        .setResultCountPerPage(42)
+                        .setOrder(SearchSpec.ORDER_ASCENDING)
+                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+                        .build();
 
         Bundle bundle = searchSpec.getBundle();
         assertThat(bundle.getInt(SearchSpec.TERM_MATCH_TYPE_FIELD))
                 .isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
-        assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD)).containsExactly(
-                "namespace1", "namespace2");
-        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD)).containsExactly(
-                "schemaTypes1", "schemaTypes2");
+        assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
+                .containsExactly("namespace1", "namespace2");
+        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
+                .containsExactly("schemaTypes1", "schemaTypes2");
         assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_FIELD)).isEqualTo(5);
         assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_PER_PROPERTY_FIELD)).isEqualTo(10);
         assertThat(bundle.getInt(SearchSpec.MAX_SNIPPET_FIELD)).isEqualTo(15);
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java
new file mode 100644
index 0000000..9c34b17
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchResultCtsTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchResultCtsTest {
+
+    @Test
+    public void testResultEquals_identical() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("String");
+
+        assertThat(result1).isEqualTo(result2);
+        assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+
+        assertThat(result3).isEqualTo(result4);
+        assertThat(result3.hashCode()).isEqualTo(result4.hashCode());
+    }
+
+    @Test
+    public void testResultEquals_failure() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("Wrong");
+        AppSearchResult<String> resultNull = AppSearchResult.newSuccessfulResult(/*value=*/ null);
+
+        assertThat(result1).isNotEqualTo(result2);
+        assertThat(result1.hashCode()).isNotEqualTo(result2.hashCode());
+        assertThat(result1).isNotEqualTo(resultNull);
+        assertThat(result1.hashCode()).isNotEqualTo(resultNull.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_IO_ERROR, "errorMessage");
+
+        assertThat(result3).isNotEqualTo(result4);
+        assertThat(result3.hashCode()).isNotEqualTo(result4.hashCode());
+
+        AppSearchResult<String> result5 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_INTERNAL_ERROR, "Wrong");
+
+        assertThat(result3).isNotEqualTo(result5);
+        assertThat(result3.hashCode()).isNotEqualTo(result5.hashCode());
+
+        AppSearchResult<String> result6 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, /*errorMessage=*/ null);
+
+        assertThat(result3).isNotEqualTo(result6);
+        assertThat(result3.hashCode()).isNotEqualTo(result6.hashCode());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java
new file mode 100644
index 0000000..2eaebd6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/AppSearchSchemaCtsTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaCtsTest {
+    @Test
+    public void testInvalidEnums() {
+        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+        expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+        expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+        IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+        builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+        builder.setSchemaType("TestType");
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+        builder.build();
+    }
+
+    @Test
+    public void testDuplicateProperties() {
+        AppSearchSchema.Builder builder =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build());
+        IllegalSchemaException e =
+                expectThrows(
+                        IllegalSchemaException.class,
+                        () ->
+                                builder.addProperty(
+                                        new PropertyConfig.Builder("subject")
+                                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                                .setIndexingType(
+                                                        PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                                .setTokenizerType(
+                                                        PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                                .build()));
+        assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java
new file mode 100644
index 0000000..657d556
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/GenericDocumentCtsTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+public class GenericDocumentCtsTest {
+    private static final byte[] sByteArray1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] sByteArray2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+    private static final GenericDocument sDocumentProperties1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .setCreationTimestampMillis(12345L)
+                    .build();
+    private static final GenericDocument sDocumentProperties2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .setCreationTimestampMillis(6789L)
+                    .build();
+
+    @Test
+    public void testDocumentEquals_identical() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_differentOrder() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Create second document with same parameter but different order.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_repeatedFieldOrder_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, true, false) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentGetSingleValue() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setScore(1)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyDouble("doubleKey1", 1.0)
+                        .setPropertyBoolean("booleanKey1", true)
+                        .setPropertyString("stringKey1", "test-value1")
+                        .setPropertyBytes("byteKey1", sByteArray1)
+                        .setPropertyDocument("documentKey1", sDocumentProperties1)
+                        .build();
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getTtlMillis()).isEqualTo(1L);
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
+        assertThat(document.getScore()).isEqualTo(1);
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+        assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyBytes("byteKey1"))
+                .asList()
+                .containsExactly((byte) 1, (byte) 2, (byte) 3);
+        assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
+    }
+
+    @Test
+    public void testDocumentGetArrayValues() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+        assertThat(document.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(document.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(document.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(sByteArray1, sByteArray2);
+        assertThat(document.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(sDocumentProperties1, sDocumentProperties2);
+    }
+
+    @Test
+    public void testDocument_toString() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString("stringKey1", "String1", "String2", "String3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        String exceptedString =
+                "{ key: 'creationTimestampMillis' value: 5 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value: "
+                        + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
+                        + "{ key: 'byteKey1' value: "
+                        + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] }  } "
+                        + "{ key: 'documentKey1' value: [ '"
+                        + "{ key: 'creationTimestampMillis' value: 12345 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties1 } ' '"
+                        + "{ key: 'creationTimestampMillis' value: 6789 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
+                        + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
+                        + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] }  } "
+                        + "{ key: 'schemaType' value: schemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: uri1 } ";
+        assertThat(document.toString()).isEqualTo(exceptedString);
+    }
+
+    @Test
+    public void testDocumentGetValues_differentTypes() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setScore(1)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Get a value for a key that doesn't exist
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+        // Get a value with a single element as an array and as a single value
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+        // Get a value with multiple elements as an array and as a single value
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+
+        // Get a value of the wrong type
+        assertThat(document.getPropertyDouble("longKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+    }
+
+    @Test
+    public void testDocumentInvalid() {
+        GenericDocument.Builder<?> builder = new GenericDocument.Builder<>("uri1", "schemaType1");
+        expectThrows(
+                IllegalArgumentException.class,
+                () -> builder.setPropertyBoolean("test", new boolean[] {}));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java
new file mode 100644
index 0000000..50bca27
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/SearchSpecCtsTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.SearchSpec;
+
+import org.junit.Test;
+
+public class SearchSpecCtsTest {
+    @Test
+    public void buildSearchSpecWithoutTermMatchType() {
+        RuntimeException e =
+                expectThrows(
+                        RuntimeException.class,
+                        () -> new SearchSpec.Builder().addSchemaType("testSchemaType").build());
+        assertThat(e).hasMessageThat().contains("Missing termMatchType field");
+    }
+
+    @Test
+    public void testBuildSearchSpec() {
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .addNamespace("namespace1", "namespace2")
+                        .addSchemaType("schemaTypes1", "schemaTypes2")
+                        .setSnippetCount(5)
+                        .setSnippetCountPerProperty(10)
+                        .setMaxSnippetSize(15)
+                        .setResultCountPerPage(42)
+                        .setOrder(SearchSpec.ORDER_ASCENDING)
+                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+                        .build();
+
+        assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
+        assertThat(searchSpec.getNamespaces())
+                .containsExactly("namespace1", "namespace2")
+                .inOrder();
+        assertThat(searchSpec.getSchemaTypes())
+                .containsExactly("schemaTypes1", "schemaTypes2")
+                .inOrder();
+        assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
+        assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
+        assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
+        assertThat(searchSpec.getResultCountPerPage()).isEqualTo(42);
+        assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
+        assertThat(searchSpec.getRankingStrategy())
+                .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java
new file mode 100644
index 0000000..29b5754
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/cts/customer/CustomerDocumentTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cts.customer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+/**
+ * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
+ * developers.
+ *
+ * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
+ * there are no package-private methods required for external developers to add custom types.
+ */
+public class CustomerDocumentTest {
+
+    private static final byte[] BYTE_ARRAY1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] BYTE_ARRAY2 = new byte[] {(byte) 4, (byte) 5, (byte) 6};
+    private static final GenericDocument DOCUMENT_PROPERTIES1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .build();
+    private static final GenericDocument DOCUMENT_PROPERTIES2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .build();
+
+    @Test
+    public void testBuildCustomerDocument() {
+        CustomerDocument customerDocument =
+                new CustomerDocument.Builder("uri1")
+                        .setScore(1)
+                        .setCreationTimestampMillis(0)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", BYTE_ARRAY1, BYTE_ARRAY2)
+                        .setPropertyDocument(
+                                "documentKey1", DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2)
+                        .build();
+
+        assertThat(customerDocument.getUri()).isEqualTo("uri1");
+        assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+        assertThat(customerDocument.getScore()).isEqualTo(1);
+        assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
+        assertThat(customerDocument.getPropertyLongArray("longKey1"))
+                .asList()
+                .containsExactly(1L, 2L, 3L);
+        assertThat(customerDocument.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(customerDocument.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(customerDocument.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(customerDocument.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(BYTE_ARRAY1, BYTE_ARRAY2);
+        assertThat(customerDocument.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2);
+    }
+
+    /**
+     * An example document type for test purposes, defined outside of {@link GenericDocument} (the
+     * way an external developer would define it).
+     */
+    private static class CustomerDocument extends GenericDocument {
+        private CustomerDocument(GenericDocument document) {
+            super(document);
+        }
+
+        public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
+            private Builder(@NonNull String uri) {
+                super(uri, "customerDocument");
+            }
+
+            @Override
+            @NonNull
+            public CustomerDocument build() {
+                return new CustomerDocument(super.build());
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java
deleted file mode 100644
index d56d0c3..0000000
--- a/core/tests/coretests/src/android/app/appsearch/external/app/customer/CustomerDocumentTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.customer;
-
-import android.annotation.NonNull;
-import android.app.appsearch.GenericDocument;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-
-/**
- * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
- * developers.
- *
- * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
- * there are no package-private methods required for external developers to add custom types.
- */
-public class CustomerDocumentTest {
-
-    private static byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
-    private static byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6};
-    private static GenericDocument sDocumentProperties1 = new GenericDocument
-            .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
-            .build();
-    private static GenericDocument sDocumentProperties2 = new GenericDocument
-            .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
-            .build();
-
-    @Test
-    public void testBuildCustomerDocument() {
-        CustomerDocument customerDocument = new CustomerDocument.Builder("uri1")
-                .setScore(1)
-                .setCreationTimestampMillis(0)
-                .setPropertyLong("longKey1", 1L, 2L, 3L)
-                .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
-                .setPropertyBoolean("booleanKey1", true, false, true)
-                .setPropertyString("stringKey1", "test-value1", "test-value2", "test-value3")
-                .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
-                .setPropertyDocument("documentKey1", sDocumentProperties1, sDocumentProperties2)
-                .build();
-
-        assertThat(customerDocument.getUri()).isEqualTo("uri1");
-        assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
-        assertThat(customerDocument.getScore()).isEqualTo(1);
-        assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
-        assertThat(customerDocument.getPropertyLongArray("longKey1")).asList()
-                .containsExactly(1L, 2L, 3L);
-        assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
-                .containsExactly(1.0, 2.0, 3.0);
-        assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList()
-                .containsExactly(true, false, true);
-        assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList()
-                .containsExactly("test-value1", "test-value2", "test-value3");
-        assertThat(customerDocument.getPropertyBytesArray("byteKey1")).asList()
-                .containsExactly(sByteArray1, sByteArray2);
-        assertThat(customerDocument.getPropertyDocumentArray("documentKey1")).asList()
-                .containsExactly(sDocumentProperties1, sDocumentProperties2);
-    }
-
-    /**
-     * An example document type for test purposes, defined outside of
-     * {@link GenericDocument} (the way an external developer would define
-     * it).
-     */
-    private static class CustomerDocument extends GenericDocument {
-        private CustomerDocument(GenericDocument document) {
-            super(document);
-        }
-
-        public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
-            private Builder(@NonNull String uri) {
-                super(uri, "customerDocument");
-            }
-
-            @Override
-            @NonNull
-            public CustomerDocument build() {
-                return new CustomerDocument(super.build());
-            }
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
new file mode 100644
index 0000000..27584a5
--- /dev/null
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PeopleSpaceTileTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+    }
+
+    @Test
+    public void testId() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertThat(tile.getId()).isEqualTo("123");
+
+        tile = new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build()).setId(
+                "5").build();
+        assertThat(tile.getId()).isEqualTo("5");
+
+        tile = new PeopleSpaceTile.Builder("12", null, null, null).build();
+        assertThat(tile.getId()).isEqualTo("12");
+    }
+
+    @Test
+    public void testUserName() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertThat(tile.getUserName()).isNull();
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setUserName("Name 1").build();
+        assertThat(tile.getUserName()).isEqualTo("Name 1");
+
+        tile = new PeopleSpaceTile.Builder(null, "Name 2", null, null).build();
+        assertThat(tile.getUserName()).isEqualTo("Name 2");
+    }
+
+    @Test
+    public void testUserIcon() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setUserIcon(
+                Icon.createWithResource(mContext, 1)).build();
+        assertThat(tile.getUserIcon().toString()).isEqualTo(
+                Icon.createWithResource(mContext, 1).toString());
+
+        tile = new PeopleSpaceTile.Builder("12", null, Icon.createWithResource(mContext, 2),
+                null).build();
+        assertThat(tile.getUserIcon().toString()).isEqualTo(
+                Icon.createWithResource(mContext, 2).toString());
+    }
+
+    @Test
+    public void testContactUri() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setContactUri(
+                Uri.parse("test")).build();
+
+        assertThat(tile.getContactUri()).isEqualTo(Uri.parse("test"));
+    }
+
+    @Test
+    public void testUid() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setUid(42).build();
+
+        assertThat(tile.getUid()).isEqualTo(42);
+    }
+
+    @Test
+    public void testPackageName() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        // Automatically added by creating a ShortcutInfo.
+        assertThat(tile.getPackageName()).isEqualTo("com.android.frameworks.coretests");
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setPackageName(
+                "package.name").build();
+        assertThat(tile.getPackageName()).isEqualTo("package.name");
+
+        tile = new PeopleSpaceTile.Builder("12", null, null,
+                new Intent().setPackage("intent.package")).build();
+        assertThat(tile.getPackageName()).isEqualTo("intent.package");
+    }
+
+    @Test
+    public void testLastInteractionTimestamp() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertThat(tile.getLastInteractionTimestamp()).isEqualTo(0L);
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setLastInteractionTimestamp(
+                7L).build();
+        assertThat(tile.getLastInteractionTimestamp()).isEqualTo(7L);
+    }
+
+    @Test
+    public void testImportantConversation() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertFalse(tile.isImportantConversation());
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setIsImportantConversation(
+                true).build();
+        assertTrue(tile.isImportantConversation());
+    }
+
+    @Test
+    public void testHiddenConversation() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertFalse(tile.isHiddenConversation());
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setIsHiddenConversation(
+                true).build();
+        assertTrue(tile.isHiddenConversation());
+    }
+
+    @Test
+    public void testNotification() {
+        Notification notification = new Notification.Builder(mContext, "test").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg" /* pkg */, "pkg" /* opPkg */,
+                1 /* id */, "" /* tag */, 0 /* uid */, 0 /* initialPid */, 0 /* score */,
+                notification, UserHandle.CURRENT, 0 /* postTime */);
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setNotification(sbn).build();
+
+        assertThat(tile.getNotification()).isEqualTo(sbn);
+    }
+
+    @Test
+    public void testIntent() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).build();
+        assertThat(tile.getIntent()).isNull();
+
+        tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build()).setIntent(new Intent()).build();
+        assertThat(tile.getIntent().toString()).isEqualTo(new Intent().toString());
+
+        tile = new PeopleSpaceTile.Builder("12", null, null, new Intent()).build();
+        assertThat(tile.getIntent().toString()).isEqualTo(new Intent().toString());
+    }
+
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
new file mode 100644
index 0000000..7effa56
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageManager.Property;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerPropertyTests {
+
+    @Test
+    public void testBooleanProperty() throws Exception {
+        final Property p = new Property("booleanProperty", true);
+        assertTrue(p.isBoolean());
+        assertFalse(p.isFloat());
+        assertFalse(p.isInteger());
+        assertFalse(p.isResourceId());
+        assertFalse(p.isString());
+        assertTrue(p.getBoolean());
+        assertEquals(0.0f, p.getFloat(), 0.0f);
+        assertEquals(0, p.getInteger());
+        assertEquals(0, p.getResourceId());
+        assertEquals(null, p.getString());
+    }
+
+    @Test
+    public void testBooleanPropertyToBundle() throws Exception {
+        final Bundle b = new Property("booleanProperty", true).toBundle(null);
+        assertTrue(b.getBoolean("booleanProperty"));
+    }
+
+    @Test
+    public void testFloatProperty() throws Exception {
+        final Property p = new Property("floatProperty", 3.14f);
+        assertFalse(p.isBoolean());
+        assertTrue(p.isFloat());
+        assertFalse(p.isInteger());
+        assertFalse(p.isResourceId());
+        assertFalse(p.isString());
+        assertFalse(p.getBoolean());
+        assertEquals(3.14f, p.getFloat(), 0.0f);
+        assertEquals(0, p.getInteger());
+        assertEquals(0, p.getResourceId());
+        assertEquals(null, p.getString());
+    }
+
+    @Test
+    public void testFloatPropertyToBundle() throws Exception {
+        final Bundle b = new Property("floatProperty", 3.14f).toBundle(null);
+        assertEquals(3.14f, b.getFloat("floatProperty"), 0.0f);
+    }
+
+    @Test
+    public void testIntegerProperty() throws Exception {
+        final Property p = new Property("integerProperty", 42, false);
+        assertFalse(p.isBoolean());
+        assertFalse(p.isFloat());
+        assertTrue(p.isInteger());
+        assertFalse(p.isResourceId());
+        assertFalse(p.isString());
+        assertFalse(p.getBoolean());
+        assertEquals(0.0f, p.getFloat(), 0.0f);
+        assertEquals(42, p.getInteger());
+        assertEquals(0, p.getResourceId());
+        assertEquals(null, p.getString());
+    }
+
+    @Test
+    public void testIntegerPropertyToBundle() throws Exception {
+        final Bundle b = new Property("integerProperty", 42, false).toBundle(null);
+        assertEquals(42, b.getInt("integerProperty"));
+    }
+
+    @Test
+    public void testResourceProperty() throws Exception {
+        final Property p = new Property("resourceProperty", 0x7f010001, true);
+        assertFalse(p.isBoolean());
+        assertFalse(p.isFloat());
+        assertFalse(p.isInteger());
+        assertTrue(p.isResourceId());
+        assertFalse(p.isString());
+        assertFalse(p.getBoolean());
+        assertEquals(0.0f, p.getFloat(), 0.0f);
+        assertEquals(0, p.getInteger());
+        assertEquals(0x7f010001, p.getResourceId());
+        assertEquals(null, p.getString());
+    }
+
+    @Test
+    public void testResourcePropertyToBundle() throws Exception {
+        final Bundle b = new Property("resourceProperty", 0x7f010001, true).toBundle(null);
+        assertEquals(0x7f010001, b.getInt("resourceProperty"));
+    }
+
+    @Test
+    public void testStringProperty() throws Exception {
+        final Property p = new Property("stringProperty", "koala");
+        assertFalse(p.isBoolean());
+        assertFalse(p.isFloat());
+        assertFalse(p.isInteger());
+        assertFalse(p.isResourceId());
+        assertTrue(p.isString());
+        assertFalse(p.getBoolean());
+        assertEquals(0.0f, p.getFloat(), 0.0f);
+        assertEquals(0, p.getInteger());
+        assertEquals(0, p.getResourceId());
+        assertEquals("koala", p.getString());
+    }
+
+    @Test
+    public void testStringPropertyToBundle() throws Exception {
+        final Bundle b = new Property("stringProperty", "koala").toBundle(null);
+        assertEquals("koala", b.getString("stringProperty"));
+    }
+
+    @Test
+    public void testProperty_invalidName() throws Exception {
+        try {
+            final Property p = new Property(null, 1);
+            fail("expected assertion error");
+        } catch (AssertionError expected) {
+        }
+    }
+
+    @Test
+    public void testProperty_invalidType() throws Exception {
+        try {
+            final Property p = new Property(null, 0);
+            fail("expected assertion error");
+        } catch (AssertionError expected) {
+        }
+
+        try {
+            final Property p = new Property(null, 6);
+            fail("expected assertion error");
+        } catch (AssertionError expected) {
+        }
+
+        try {
+            final Property p = new Property(null, -1);
+            fail("expected assertion error");
+        } catch (AssertionError expected) {
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index e17800f..2402420 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -77,7 +77,7 @@
 
         Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
         mTracker = Mockito.spy(
-                new FrameTracker(session, handler, mRenderer, mWrapper));
+                new FrameTracker(session, handler, mRenderer, mWrapper, 1, -1));
         doNothing().when(mTracker).triggerPerfetto();
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index a9cfd28..474cb1d 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -24,6 +24,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -32,6 +33,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.provider.DeviceConfig;
 import android.view.View;
 import android.view.ViewAttachTestActivity;
 
@@ -50,6 +52,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -71,8 +74,6 @@
         mView = mActivity.getWindow().getDecorView();
         assertThat(mView.isAttachedToWindow()).isTrue();
 
-        InteractionJankMonitor.abandon();
-
         Handler handler = spy(new Handler(mActivity.getMainLooper()));
         doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
         mWorker = spy(new HandlerThread("Interaction-jank-monitor-test"));
@@ -93,7 +94,7 @@
         Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
         FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
                 new ThreadedRendererWrapper(mView.getThreadedRenderer()),
-                new FrameMetricsWrapper()));
+                new FrameMetricsWrapper(), 1, -1));
         doReturn(tracker).when(monitor).createFrameTracker(any());
 
         // Simulate a trace session and see if begin / end are invoked.
@@ -104,6 +105,21 @@
     }
 
     @Test
+    public void testDisabledThroughDeviceConfig() {
+        InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
+        monitor.init(mView);
+
+        HashMap<String, String> propertiesValues = new HashMap<>();
+        propertiesValues.put("enabled", "false");
+        DeviceConfig.Properties properties = new DeviceConfig.Properties(
+                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
+        monitor.getPropertiesChangedListener().onPropertiesChanged(properties);
+
+        assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+    }
+
+    @Test
     public void testCheckInitState() {
         InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
 
@@ -134,12 +150,13 @@
         Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
         FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
                 new ThreadedRendererWrapper(mView.getThreadedRenderer()),
-                new FrameMetricsWrapper()));
+                new FrameMetricsWrapper(), 1, -1));
         doReturn(tracker).when(monitor).createFrameTracker(any());
 
         assertThat(monitor.begin(session.getCuj())).isTrue();
         verify(tracker).begin();
-        verify(mWorker.getThreadHandler()).sendMessageAtTime(captor.capture(), anyLong());
+        verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(),
+                anyLong());
         Runnable runnable = captor.getValue().getCallback();
         assertThat(runnable).isNotNull();
         mWorker.getThreadHandler().removeCallbacks(runnable);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 4094f83..306388f 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -191,6 +191,10 @@
         }
 
         @Override
+        public void toggleAndFollowTvPower() {
+        }
+
+        @Override
         public void queryDisplayStatus(final IHdmiControlCallback callback) {
         }
 
diff --git a/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java b/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
new file mode 100644
index 0000000..8b42ff7
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.uwb;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link SessionHandle}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SessionHandleTest {
+
+    @Test
+    public void testBasic() {
+        int handleId = 12;
+        SessionHandle handle = new SessionHandle(handleId);
+        assertEquals(handle.getId(), handleId);
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        SessionHandle handle = new SessionHandle(10);
+        handle.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
+        assertEquals(handle, fromParcel);
+    }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 745de84..3e3aefc 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -156,4 +156,11 @@
     sub_dir: "permissions",
     src: "com.android.car.provision.xml",
     filename_from_src: true,
-}
\ No newline at end of file
+}
+
+prebuilt_etc {
+    name: "allowed_privapp_com.android.carshell",
+    sub_dir: "permissions",
+    src: "com.android.car.shell.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
new file mode 100644
index 0000000..32666c8
--- /dev/null
+++ b/data/etc/car/com.android.car.shell.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.shell">
+        <permission name="android.permission.INSTALL_PACKAGES" />
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index fe1182e..e473c55 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -56,5 +56,7 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
+        <permission name="android.permission.READ_DREAM_STATE"/>
+        <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index eba7306..712349a 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -810,18 +810,16 @@
          */
         public @NonNull Typeface build() {
             final int userFallbackSize = mFamilies.size();
-            final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
-            final long[] ptrArray = new long[fallback.length + userFallbackSize];
+            final Typeface fallbackTypeface = getSystemDefaultTypeface(mFallbackName);
+            final long[] ptrArray = new long[userFallbackSize];
             for (int i = 0; i < userFallbackSize; ++i) {
                 ptrArray[i] = mFamilies.get(i).getNativePtr();
             }
-            for (int i = 0; i < fallback.length; ++i) {
-                ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
-            }
             final int weight = mStyle == null ? 400 : mStyle.getWeight();
             final int italic =
                     (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ?  0 : 1;
-            return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
+            return new Typeface(nativeCreateFromArray(
+                    ptrArray, fallbackTypeface.native_instance, weight, italic));
         }
     }
 
@@ -1058,7 +1056,8 @@
             ptrArray[i] = families[i].mNativePtr;
         }
         return new Typeface(nativeCreateFromArray(
-                ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+                ptrArray, 0, RESOLVE_BY_FONT_TABLE,
+                RESOLVE_BY_FONT_TABLE));
     }
 
     /**
@@ -1071,7 +1070,7 @@
         for (int i = 0; i < families.length; ++i) {
             ptrArray[i] = families[i].getNativePtr();
         }
-        return new Typeface(nativeCreateFromArray(ptrArray,
+        return new Typeface(nativeCreateFromArray(ptrArray, 0,
                   RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     }
 
@@ -1106,15 +1105,13 @@
     @Deprecated
     private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
                 String fallbackName, int weight, int italic) {
-        android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName);
-        long[] ptrArray = new long[families.length + fallback.length];
+        final Typeface fallbackTypeface = getSystemDefaultTypeface(fallbackName);
+        long[] ptrArray = new long[families.length];
         for (int i = 0; i < families.length; i++) {
             ptrArray[i] = families[i].mNativePtr;
         }
-        for (int i = 0; i < fallback.length; i++) {
-            ptrArray[i + families.length] = fallback[i].getNativePtr();
-        }
-        return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
+        return new Typeface(nativeCreateFromArray(
+                ptrArray, fallbackTypeface.native_instance, weight, italic));
     }
 
     // don't allow clients to call this directly
@@ -1379,7 +1376,8 @@
     @UnsupportedAppUsage
     private static native long nativeCreateWeightAlias(long native_instance, int weight);
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
+    private static native long nativeCreateFromArray(
+            long[] familyArray, long fallbackTypeface, int weight, int italic);
     private static native int[] nativeGetSupportedAxes(long native_instance);
 
     @CriticalNative
diff --git a/graphics/java/android/graphics/drawable/TEST_MAPPING b/graphics/java/android/graphics/drawable/TEST_MAPPING
new file mode 100644
index 0000000..1018702
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+  "presubmit": [
+    {
+
+      "name": "CtsGraphicsTestCases",
+      "file_patterns": ["(/|^)Icon\\.java"],
+      "options" : [
+        {
+          "include-filter": "android.graphics.drawable.cts.IconTest"
+        }
+      ]
+    },
+    {
+
+      "name": "FrameworksCoreTests",
+      "file_patterns": ["(/|^)Icon\\.java"],
+      "options" : [
+        {
+          "include-filter": "android.graphics.drawable.IconTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 95a8417..95c4706 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -67,19 +67,6 @@
     }
 
     /**
-     * Returns fallback list for the given family name.
-     *
-     * If no fallback found for the given family name, returns fallback for the default family.
-     *
-     * @param familyName family name, e.g. "serif"
-     * @hide
-     */
-    public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) {
-        final FontFamily[] families = sSystemFallbackMap.get(familyName);
-        return families == null ? sSystemFallbackMap.get(DEFAULT_FAMILY) : families;
-    }
-
-    /**
      * Returns raw system fallback map.
      *
      * This method is intended to be used only by Typeface static initializer.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 3ac9d68..5867ef6 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -84,7 +84,7 @@
 
         // java.security.KeyStore
         put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
-        put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
+        put("Alg.Alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
 
         // java.security.KeyPairGenerator
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index 973ae8e..d33bf99 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -20,5 +20,5 @@
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Відарыс у відарысе"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
     <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
-    <string name="pip_fullscreen" msgid="7278047353591302554">"Ва ўвесь экран"</string>
+    <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 0432f1c..02cce9d 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -17,8 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
-    <skip />
+    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild im Bild"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index 7aaf79f..c74e0bb 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -17,8 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
-    <skip />
+    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ചിത്രത്തിനുള്ളിൽ ചിത്രം"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
     <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്‍ണ്ണ സ്ക്രീന്‍"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index 64e5db5..3179533 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -17,8 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for notification_channel_tv_pip (2576686079160402435) -->
-    <skip />
+    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر میں تصویر"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
     <string name="pip_close" msgid="9135220303720555525">"‏PIP بند کریں"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c913e0c..f0eae97 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -30,6 +30,12 @@
     <!-- Animation duration when using long press on recents to dock -->
     <integer name="long_press_dock_anim_duration">250</integer>
 
+    <!-- Animation duration for translating of one handed when trigger / dismiss. -->
+    <integer name="config_one_handed_translate_animation_duration">300</integer>
+
+    <!-- One handed mode default offset % of display size -->
+    <fraction name="config_one_handed_offset">40%</fraction>
+
     <!-- Allow one handed to enable round corner -->
     <bool name="config_one_handed_enable_round_corner">true</bool>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index beac59b..aa7355b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -89,6 +89,7 @@
 
     // TODO(b/173386799) keep in sync with Launcher3 and also don't do a broadcast
     public static final String TASKBAR_CHANGED_BROADCAST = "taskbarChanged";
+    public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated";
     public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened";
     public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible";
     public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition";
@@ -350,12 +351,15 @@
                 + " itemPosition: " + itemPosition[0] + "," + itemPosition[1]
                 + " iconSize: " + iconSize);
         PointF point = new PointF(itemPosition[0], itemPosition[1]);
-        mBubblePositioner.setPinnedLocation(point);
+        mBubblePositioner.setPinnedLocation(isVisible ? point : null);
         mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize);
         if (mStackView != null) {
-            if (isVisible) {
-                mStackView.updateStackPosition();
+            if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) {
+                // If taskbar was created, add and remove the window so that bubbles display on top
+                removeFromWindowManagerMaybe();
+                addToWindowManagerMaybe();
             }
+            mStackView.updateStackPosition();
             mBubbleIconFactory = new BubbleIconFactory(mContext);
             mStackView.onDisplaySizeChanged();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index f84936e..e8c6cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -205,8 +206,11 @@
         mGestureHandler = gestureHandler;
         mOverlayManager = overlayManager;
 
-        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50)
-                / 100.0f;
+        final float offsetPercentageConfig = context.getResources().getFraction(
+                R.fraction.config_one_handed_offset, 1, 1);
+        final int sysPropPercentageConfig = SystemProperties.getInt(
+                ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+        mOffSetFraction = sysPropPercentageConfig / 100.0f;
         mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                 context.getContentResolver());
         mIsSwipeToNotificationEnabled =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index bd6c1e0..0311030 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -40,6 +40,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.os.SomeArgs;
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayController;
 
 import java.io.PrintWriter;
@@ -156,8 +157,11 @@
         mDisplayController = displayController;
         mDefaultDisplayBounds.set(getDisplayBounds());
         mLastVisualDisplayBounds.set(getDisplayBounds());
+        final int animationDurationConfig = context.getResources().getInteger(
+                R.integer.config_one_handed_translate_animation_duration);
         mEnterExitAnimationDurationMs =
-                SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, 300);
+                SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
+                        animationDurationConfig);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mTutorialHandler = tutorialHandler;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index b6b518d..d65ad62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -86,8 +86,11 @@
                 context.getSystemService(Context.ACCESSIBILITY_SERVICE);
         mTargetViewContainer = new FrameLayout(context);
         mTargetViewContainer.setClipChildren(false);
-        mTutorialAreaHeight = Math.round(mDisplaySize.y
-                * (SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f));
+        final float offsetPercentageConfig = context.getResources().getFraction(
+                R.fraction.config_one_handed_offset, 1, 1);
+        final int sysPropPercentageConfig = SystemProperties.getInt(
+                ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+        mTutorialAreaHeight = Math.round(mDisplaySize.y * (sysPropPercentageConfig / 100.0f));
         mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
         mTargetViewContainer.addView(mTutorialView);
         mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index df6683e..1bb5eda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.util.Size;
@@ -42,6 +43,9 @@
     private final @NonNull PipBoundsState mPipBoundsState;
     private final PipSnapAlgorithm mSnapAlgorithm;
 
+    private float mDefaultSizePercent;
+    private float mMinAspectRatioForMinSize;
+    private float mMaxAspectRatioForMinSize;
     private float mDefaultAspectRatio;
     private float mMinAspectRatio;
     private float mMaxAspectRatio;
@@ -51,7 +55,7 @@
 
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) {
         mPipBoundsState = pipBoundsState;
-        mSnapAlgorithm = new PipSnapAlgorithm(context);
+        mSnapAlgorithm = new PipSnapAlgorithm();
         reloadResources(context);
         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -83,6 +87,11 @@
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
         mMaxAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
+        mDefaultSizePercent = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
+        mMaxAspectRatioForMinSize = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
     }
 
     /**
@@ -174,7 +183,7 @@
             final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
                     : defaultMinEdgeSize;
             // Use the existing size but adjusted to the aspect ratio and min edge size.
-            size = mSnapAlgorithm.getSizeForAspectRatio(
+            size = getSizeForAspectRatio(
                     new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
         } else {
             if (overrideMinSize != null) {
@@ -184,7 +193,7 @@
             } else {
                 // Calculate the default size using the display size and default min edge size.
                 final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
-                size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
+                size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
                         displayInfo.logicalWidth, displayInfo.logicalHeight);
             }
         }
@@ -229,7 +238,7 @@
                 defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
             } else {
                 // Calculate the default size using the display size and default min edge size.
-                defaultSize = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
+                defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
                         mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
             }
             Gravity.apply(mDefaultStackGravity, defaultSize.getWidth(), defaultSize.getHeight(),
@@ -270,13 +279,28 @@
         getInsetBounds(movementBounds);
 
         // Apply the movement bounds adjustments based on the current state.
-        mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+        getMovementBounds(stackBounds, movementBounds, movementBounds,
                 (adjustForIme && mPipBoundsState.isImeShowing())
                         ? mPipBoundsState.getImeHeight() : 0);
+
         return movementBounds;
     }
 
     /**
+     * Adjusts movementBoundsOut so that it is the movement bounds for the given stackBounds.
+     */
+    public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
+            int bottomOffset) {
+        // Adjust the right/bottom to ensure the stack bounds never goes offscreen
+        movementBoundsOut.set(insetBounds);
+        movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right
+                - stackBounds.width());
+        movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom
+                - stackBounds.height());
+        movementBoundsOut.bottom -= bottomOffset;
+    }
+
+    /**
      * @return the default snap fraction to apply instead of the default gravity when calculating
      *         the default stack bounds when first entering PiP.
      */
@@ -304,6 +328,62 @@
     }
 
     /**
+     * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
+     * is at least minEdgeSize.
+     */
+    public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
+            int displayHeight) {
+        final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
+        final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
+
+        final int width;
+        final int height;
+        if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
+            // Beyond these points, we can just use the min size as the shorter edge
+            if (aspectRatio <= 1) {
+                // Portrait, width is the minimum size
+                width = minSize;
+                height = Math.round(width / aspectRatio);
+            } else {
+                // Landscape, height is the minimum size
+                height = minSize;
+                width = Math.round(height * aspectRatio);
+            }
+        } else {
+            // Within these points, we ensure that the bounds fit within the radius of the limits
+            // at the points
+            final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
+            final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
+            height = (int) Math.round(Math.sqrt((radius * radius)
+                    / (aspectRatio * aspectRatio + 1)));
+            width = Math.round(height * aspectRatio);
+        }
+        return new Size(width, height);
+    }
+
+    /**
+     * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
+     * minimum edge is at least minEdgeSize.
+     */
+    public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
+        final int smallestSize = Math.min(size.getWidth(), size.getHeight());
+        final int minSize = (int) Math.max(minEdgeSize, smallestSize);
+
+        final int width;
+        final int height;
+        if (aspectRatio <= 1) {
+            // Portrait, width is the minimum size.
+            width = minSize;
+            height = Math.round(width / aspectRatio);
+        } else {
+            // Landscape, height is the minimum size
+            height = minSize;
+            width = Math.round(height * aspectRatio);
+        }
+        return new Size(width, height);
+    }
+
+    /**
      * Dumps internal states.
      */
     public void dump(PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index ce1139b..53aa614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -59,42 +59,39 @@
     private final @NonNull Rect mExpandedBounds = new Rect();
     private final @NonNull Rect mNormalMovementBounds = new Rect();
     private final @NonNull Rect mExpandedMovementBounds = new Rect();
-    private final Context mContext;
+    private final @NonNull Context mContext;
     private float mAspectRatio;
     private int mStashedState = STASH_TYPE_NONE;
     private int mStashOffset;
-    private PipReentryState mPipReentryState;
-    private ComponentName mLastPipComponentName;
-    private final DisplayInfo mDisplayInfo = new DisplayInfo();
-    private final DisplayLayout mDisplayLayout = new DisplayLayout();
+    private @Nullable PipReentryState mPipReentryState;
+    private @Nullable ComponentName mLastPipComponentName;
+    private final @NonNull DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
     /** The current minimum edge size of PIP. */
     private int mMinEdgeSize;
     /** The preferred minimum (and default) size specified by apps. */
-    private Size mOverrideMinSize;
-    private final @NonNull AnimatingBoundsState mAnimatingBoundsState = new AnimatingBoundsState();
+    private @Nullable Size mOverrideMinSize;
+    private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
     private boolean mIsImeShowing;
     private int mImeHeight;
     private boolean mIsShelfShowing;
     private int mShelfHeight;
 
-    private Runnable mOnMinimalSizeChangeCallback;
-    private BiConsumer<Boolean, Integer> mOnShelfVisibilityChangeCallback;
+    private @Nullable Runnable mOnMinimalSizeChangeCallback;
+    private @Nullable BiConsumer<Boolean, Integer> mOnShelfVisibilityChangeCallback;
 
-    public PipBoundsState(Context context) {
+    public PipBoundsState(@NonNull Context context) {
         mContext = context;
         reloadResources();
     }
 
-    /**
-     * Reloads the resources.
-     */
+    /** Reloads the resources. */
     public void onConfigurationChanged() {
         reloadResources();
     }
 
     private void reloadResources() {
-        mStashOffset = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.pip_stash_offset);
+        mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
     }
 
     /** Set the current PIP bounds. */
@@ -102,12 +99,14 @@
         mBounds.set(bounds);
     }
 
+    /** Get the current PIP bounds. */
     @NonNull
     public Rect getBounds() {
         return new Rect(mBounds);
     }
 
     /** Returns the current movement bounds. */
+    @NonNull
     public Rect getMovementBounds() {
         return mMovementBounds;
     }
@@ -135,28 +134,28 @@
     }
 
     /** Set the normal movement bounds. */
-    public void setNormalMovementBounds(Rect bounds) {
+    public void setNormalMovementBounds(@NonNull Rect bounds) {
         mNormalMovementBounds.set(bounds);
     }
 
     /** Returns the normal movement bounds. */
+    @NonNull
     public Rect getNormalMovementBounds() {
         return mNormalMovementBounds;
     }
 
     /** Set the expanded movement bounds. */
-    public void setExpandedMovementBounds(Rect bounds) {
+    public void setExpandedMovementBounds(@NonNull Rect bounds) {
         mExpandedMovementBounds.set(bounds);
     }
 
     /** Returns the expanded movement bounds. */
+    @NonNull
     public Rect getExpandedMovementBounds() {
         return mExpandedMovementBounds;
     }
 
-    /**
-     * Dictate where PiP currently should be stashed, if at all.
-     */
+    /** Dictate where PiP currently should be stashed, if at all. */
     public void setStashed(@StashType int stashedState) {
         mStashedState = stashedState;
     }
@@ -169,50 +168,39 @@
         return mStashedState;
     }
 
-    /**
-     * Whether PiP is stashed or not.
-     */
+    /** Whether PiP is stashed or not. */
     public boolean isStashed() {
         return mStashedState != STASH_TYPE_NONE;
     }
 
-    /**
-     * Returns the offset from the edge of the screen for PiP stash.
-     */
+    /** Returns the offset from the edge of the screen for PiP stash. */
     public int getStashOffset() {
         return mStashOffset;
     }
 
+    /** Set the PIP aspect ratio. */
     public void setAspectRatio(float aspectRatio) {
         mAspectRatio = aspectRatio;
     }
 
+    /** Get the PIP aspect ratio. */
     public float getAspectRatio() {
         return mAspectRatio;
     }
 
-    /**
-     * Save the reentry state to restore to when re-entering PIP mode.
-     *
-     * TODO(b/169373982): consider refactoring this so that this class alone can use mBounds and
-     * calculate the snap fraction to save for re-entry.
-     */
+    /** Save the reentry state to restore to when re-entering PIP mode. */
     public void saveReentryState(@NonNull Rect bounds, float fraction) {
         mPipReentryState = new PipReentryState(new Size(bounds.width(), bounds.height()), fraction);
     }
 
-    /**
-     * Returns the saved reentry state.
-     */
+    /** Returns the saved reentry state. */
     @Nullable
     public PipReentryState getReentryState() {
         return mPipReentryState;
     }
 
-    /**
-     * Set the last {@link ComponentName} to enter PIP mode.
-     */
-    public void setLastPipComponentName(ComponentName lastPipComponentName) {
+    /** Set the last {@link ComponentName} to enter PIP mode. */
+    public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) {
         final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
         mLastPipComponentName = lastPipComponentName;
         if (changed) {
@@ -220,41 +208,40 @@
         }
     }
 
+    /** Get the last PIP component name, if any. */
+    @Nullable
     public ComponentName getLastPipComponentName() {
         return mLastPipComponentName;
     }
 
+    /** Get the current display info. */
     @NonNull
     public DisplayInfo getDisplayInfo() {
         return mDisplayInfo;
     }
 
-    /**
-     * Update the display info.
-     */
+    /** Update the display info. */
     public void setDisplayInfo(@NonNull DisplayInfo displayInfo) {
         mDisplayInfo.copyFrom(displayInfo);
     }
 
+    /** Set the rotation of the display. */
     public void setDisplayRotation(int rotation) {
         mDisplayInfo.rotation = rotation;
     }
 
-    /**
-     * Returns the display's bound.
-     */
+    /** Returns the display's bounds. */
     @NonNull
     public Rect getDisplayBounds() {
         return new Rect(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
     }
 
-    /**
-     * Update the display layout.
-     */
+    /** Update the display layout. */
     public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
         mDisplayLayout.set(displayLayout);
     }
 
+    /** Get the display layout. */
     @NonNull
     public DisplayLayout getDisplayLayout() {
         return mDisplayLayout;
@@ -275,10 +262,8 @@
         return mMinEdgeSize;
     }
 
-    /**
-     * Sets the preferred size of PIP as specified by the activity in PIP mode.
-     */
-    public void setOverrideMinSize(Size overrideMinSize) {
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+    public void setOverrideMinSize(@Nullable Size overrideMinSize) {
         final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
         mOverrideMinSize = overrideMinSize;
         if (changed && mOnMinimalSizeChangeCallback != null) {
@@ -287,6 +272,7 @@
     }
 
     /** Returns the preferred minimal size specified by the activity in PIP. */
+    @Nullable
     public Size getOverrideMinSize() {
         return mOverrideMinSize;
     }
@@ -297,8 +283,10 @@
         return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
     }
 
-    public AnimatingBoundsState getAnimatingBoundsState() {
-        return mAnimatingBoundsState;
+    /** Get the state of the bounds in motion. */
+    @NonNull
+    public MotionBoundsState getMotionBoundsState() {
+        return mMotionBoundsState;
     }
 
     /** Set whether the IME is currently showing and its height. */
@@ -344,41 +332,41 @@
     /**
      * Registers a callback when the minimal size of PIP that is set by the app changes.
      */
-    public void setOnMinimalSizeChangeCallback(Runnable onMinimalSizeChangeCallback) {
+    public void setOnMinimalSizeChangeCallback(@Nullable Runnable onMinimalSizeChangeCallback) {
         mOnMinimalSizeChangeCallback = onMinimalSizeChangeCallback;
     }
 
     /** Set a callback to be notified when the shelf visibility changes. */
     public void setOnShelfVisibilityChangeCallback(
-            BiConsumer<Boolean, Integer> onShelfVisibilityChangeCallback) {
+            @Nullable BiConsumer<Boolean, Integer> onShelfVisibilityChangeCallback) {
         mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
     }
 
-    /** Source of truth for the current animation bounds of PIP. */
-    public static class AnimatingBoundsState {
-        /** The bounds used when PIP is being dragged or animated. */
-        private final Rect mTemporaryBounds = new Rect();
+    /** Source of truth for the current bounds of PIP that may be in motion. */
+    public static class MotionBoundsState {
+        /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
+        private final @NonNull Rect mBoundsInMotion = new Rect();
         /** The destination bounds to which PIP is animating. */
-        private final Rect mAnimatingToBounds = new Rect();
+        private final @NonNull Rect mAnimatingToBounds = new Rect();
 
         /** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
-        public boolean isAnimating() {
-            return !mTemporaryBounds.isEmpty();
+        public boolean isInMotion() {
+            return !mBoundsInMotion.isEmpty();
         }
 
         /** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
-        public void setTemporaryBounds(Rect bounds) {
-            mTemporaryBounds.set(bounds);
+        public void setBoundsInMotion(@NonNull Rect bounds) {
+            mBoundsInMotion.set(bounds);
         }
 
         /** Set the bounds to which PIP is animating. */
-        public void setAnimatingToBounds(Rect bounds) {
+        public void setAnimatingToBounds(@NonNull Rect bounds) {
             mAnimatingToBounds.set(bounds);
         }
 
-        /** Called when all ongoing dragging and animation operations have ended. */
+        /** Called when all ongoing motion operations have ended. */
         public void onAllAnimationsEnded() {
-            mTemporaryBounds.setEmpty();
+            mBoundsInMotion.setEmpty();
         }
 
         /** Called when an ongoing physics animation has ended. */
@@ -386,20 +374,22 @@
             mAnimatingToBounds.setEmpty();
         }
 
-        /** Returns the temporary animation bounds. */
-        public Rect getTemporaryBounds() {
-            return mTemporaryBounds;
+        /** Returns the motion bounds. */
+        @NonNull
+        public Rect getBoundsInMotion() {
+            return mBoundsInMotion;
         }
 
         /** Returns the destination bounds to which PIP is currently animating. */
+        @NonNull
         public Rect getAnimatingToBounds() {
             return mAnimatingToBounds;
         }
 
         void dump(PrintWriter pw, String prefix) {
             final String innerPrefix = prefix + "  ";
-            pw.println(prefix + AnimatingBoundsState.class.getSimpleName());
-            pw.println(innerPrefix + "mTemporaryBounds=" + mTemporaryBounds);
+            pw.println(prefix + MotionBoundsState.class.getSimpleName());
+            pw.println(innerPrefix + "mBoundsInMotion=" + mBoundsInMotion);
             pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
         }
     }
@@ -432,9 +422,7 @@
         }
     }
 
-    /**
-     * Dumps internal state.
-     */
+    /** Dumps internal state. */
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
@@ -461,6 +449,6 @@
         } else {
             mPipReentryState.dump(pw, innerPrefix);
         }
-        mAnimatingBoundsState.dump(pw, innerPrefix);
+        mMotionBoundsState.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
index 7106075..0528e4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
@@ -20,11 +20,9 @@
 import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
 import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
 
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PointF;
 import android.graphics.Rect;
-import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Calculates the snap targets and the snap position for the PIP given a position and a velocity.
@@ -32,19 +30,6 @@
  */
 public class PipSnapAlgorithm {
 
-    private final float mDefaultSizePercent;
-    private final float mMinAspectRatioForMinSize;
-    private final float mMaxAspectRatioForMinSize;
-
-    public PipSnapAlgorithm(Context context) {
-        Resources res = context.getResources();
-        mDefaultSizePercent = res.getFloat(
-                com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
-        mMaxAspectRatioForMinSize = res.getFloat(
-                com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
-        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
-    }
-
     /**
      * Returns a fraction that describes where the PiP bounds is.
      * See {@link #getSnapFraction(Rect, Rect, int)}.
@@ -136,81 +121,11 @@
     }
 
     /**
-     * Adjusts {@param movementBoundsOut} so that it is the movement bounds for the given
-     * {@param stackBounds}.
-     */
-    public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
-            int bottomOffset) {
-        // Adjust the right/bottom to ensure the stack bounds never goes offscreen
-        movementBoundsOut.set(insetBounds);
-        movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right -
-                stackBounds.width());
-        movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom -
-                stackBounds.height());
-        movementBoundsOut.bottom -= bottomOffset;
-    }
-
-    /**
-     * @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge
-     * is at least {@param minEdgeSize}.
-     */
-    public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
-            int displayHeight) {
-        final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
-        final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
-        final int width;
-        final int height;
-        if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
-            // Beyond these points, we can just use the min size as the shorter edge
-            if (aspectRatio <= 1) {
-                // Portrait, width is the minimum size
-                width = minSize;
-                height = Math.round(width / aspectRatio);
-            } else {
-                // Landscape, height is the minimum size
-                height = minSize;
-                width = Math.round(height * aspectRatio);
-            }
-        } else {
-            // Within these points, we ensure that the bounds fit within the radius of the limits
-            // at the points
-            final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
-            final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
-            height = (int) Math.round(Math.sqrt((radius * radius) /
-                    (aspectRatio * aspectRatio + 1)));
-            width = Math.round(height * aspectRatio);
-        }
-        return new Size(width, height);
-    }
-
-    /**
-     * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
-     * minimum edge is at least minEdgeSize.
-     */
-    public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
-        final int smallestSize = Math.min(size.getWidth(), size.getHeight());
-        final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
-        final int width;
-        final int height;
-        if (aspectRatio <= 1) {
-            // Portrait, width is the minimum size.
-            width = minSize;
-            height = Math.round(width / aspectRatio);
-        } else {
-            // Landscape, height is the minimum size
-            height = minSize;
-            width = Math.round(height * aspectRatio);
-        }
-        return new Size(width, height);
-    }
-
-    /**
      * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
      * the new bounds out to {@param boundsOut}.
      */
-    public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+    @VisibleForTesting
+    void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
             @PipBoundsState.StashType int stashType) {
         int leftEdge = stackBounds.left;
         if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8fa9448..903f7d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -90,7 +90,7 @@
             });
 
     /**
-     * PhysicsAnimator instance for animating {@link PipBoundsState#getAnimatingBoundsState()}
+     * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
      * using physics animations.
      */
     private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
@@ -98,7 +98,7 @@
     private MagnetizedObject<Rect> mMagnetizedPip;
 
     /**
-     * Update listener that resizes the PIP to {@link PipBoundsState#getAnimatingBoundsState()}.
+     * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
      */
     private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
 
@@ -172,14 +172,14 @@
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
         mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
-                mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
+                mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
         mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
                 mSfAnimationHandlerThreadLocal.get());
 
         mResizePipUpdateListener = (target, values) -> {
-            if (mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+            if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
-                        mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(), null);
+                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
             }
         };
     }
@@ -187,8 +187,8 @@
     @NonNull
     @Override
     public Rect getFloatingBoundsOnScreen() {
-        return !mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds().isEmpty()
-                ? mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds() : getBounds();
+        return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+                ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
     }
 
     @NonNull
@@ -207,7 +207,7 @@
      */
     void synchronizePinnedStackBounds() {
         cancelPhysicsAnimation();
-        mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
+        mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
 
         if (mPipTaskOrganizer.isInPip()) {
             mFloatingContentCoordinator.onContentMoved(this);
@@ -242,7 +242,7 @@
                 resizePipUnchecked(toBounds);
                 mPipBoundsState.setBounds(toBounds);
             } else {
-                mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(toBounds);
+                mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
                         (Rect newBounds) -> {
                             mMainHandler.post(() -> {
@@ -278,8 +278,8 @@
 
         // If we're already in the dismiss target area, then there won't be a move to set the
         // temporary bounds, so just initialize it to the current bounds.
-        if (!mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
-            mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
+        if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+            mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
         }
         mTemporaryBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
@@ -396,7 +396,7 @@
 
         final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
 
-        final int startValueY = mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds().top;
+        final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
         final float estimatedFlingYEndValue =
                 PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
 
@@ -411,7 +411,7 @@
     void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
         if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
             // Animate from the current bounds if we're not already animating.
-            mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
+            mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
         }
 
         mTemporaryBoundsPhysicsAnimator
@@ -492,7 +492,7 @@
      */
     private void cancelPhysicsAnimation() {
         mTemporaryBoundsPhysicsAnimator.cancel();
-        mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
+        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
         mSpringingToTouch = false;
     }
 
@@ -565,17 +565,17 @@
         if (!mDismissalPending
                 && !mSpringingToTouch
                 && !mMagnetizedPip.getObjectStuckToTarget()) {
-            // All animations (including dragging) have actually finished.
+            // All motion operations have actually finished.
             mPipBoundsState.setBounds(
-                    mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
-            mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
+                    mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+            mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
             if (!mDismissalPending) {
                 // do not schedule resize if PiP is dismissing, which may cause app re-open to
                 // mBounds instead of it's normal bounds.
                 mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
             }
         }
-        mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
+        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
         mSpringingToTouch = false;
         mDismissalPending = false;
     }
@@ -586,7 +586,7 @@
      * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
      */
     private void setAnimatingToBounds(Rect bounds) {
-        mPipBoundsState.getAnimatingBoundsState().setAnimatingToBounds(bounds);
+        mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
         mFloatingContentCoordinator.onContentMoved(this);
     }
 
@@ -625,7 +625,7 @@
     MagnetizedObject<Rect> getMagnetizedPip() {
         if (mMagnetizedPip == null) {
             mMagnetizedPip = new MagnetizedObject<Rect>(
-                    mContext, mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(),
+                    mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
                     FloatProperties.RECT_X, FloatProperties.RECT_Y) {
                 @Override
                 public float getWidth(@NonNull Rect animatedPipBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 1c5d5b8..a78c4ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -307,8 +307,7 @@
 
     public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
         final Rect toMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds,
-                toMovementBounds, 0);
+        mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
         final int prevBottom = mPipBoundsState.getMovementBounds().bottom
                 - mMovementBoundsExtraOffsets;
         if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
@@ -339,13 +338,13 @@
 
         // Re-calculate the expanded bounds
         Rect normalMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(normalBounds, insetBounds,
+        mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
                 normalMovementBounds, bottomOffset);
 
         if (mPipBoundsState.getMovementBounds().isEmpty()) {
             // mMovementBounds is not initialized yet and a clean movement bounds without
             // bottom offset shall be used later in this function.
-            mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
+            mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
                     mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
         }
 
@@ -353,12 +352,12 @@
         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
         Point displaySize = new Point();
         mContext.getDisplay().getRealSize(displaySize);
-        Size expandedSize = mPipBoundsAlgorithm.getSnapAlgorithm().getSizeForAspectRatio(
+        Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
                 aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
         mPipBoundsState.setExpandedBounds(
                 new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
         Rect expandedMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(
+        mPipBoundsAlgorithm.getMovementBounds(
                 mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
                 bottomOffset);
 
@@ -381,7 +380,7 @@
             } else {
                 final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
                 final Rect toMovementBounds = new Rect();
-                mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
+                mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
                         toMovementBounds, mIsImeShowing ? mImeHeight : 0);
                 final int prevBottom = mPipBoundsState.getMovementBounds().bottom
                         - mMovementBoundsExtraOffsets;
@@ -659,7 +658,7 @@
 
     private void animateToUnexpandedState(Rect restoreBounds) {
         Rect restoredMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(restoreBounds,
+        mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
                 mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
                 restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
@@ -707,7 +706,7 @@
                 return;
             }
 
-            Rect bounds = getPossiblyAnimatingBounds();
+            Rect bounds = getPossiblyMotionBounds();
             mDelta.set(0f, 0f);
             mStartPosition.set(bounds.left, bounds.top);
             mMovementWithinDismiss = touchState.getDownTouchPosition().y
@@ -746,7 +745,7 @@
                 mDelta.x += left - lastX;
                 mDelta.y += top - lastY;
 
-                mTmpBounds.set(getPossiblyAnimatingBounds());
+                mTmpBounds.set(getPossiblyMotionBounds());
                 mTmpBounds.offsetTo((int) left, (int) top);
                 mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
 
@@ -865,7 +864,7 @@
      * resized.
      */
     private void updateMovementBounds() {
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(mPipBoundsState.getBounds(),
+        mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
                 mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.onMovementBoundsChanged();
 
@@ -877,7 +876,7 @@
 
     private Rect getMovementBounds(Rect curBounds) {
         Rect movementBounds = new Rect();
-        mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, mInsetBounds,
+        mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
                 movementBounds, mIsImeShowing ? mImeHeight : 0);
         return movementBounds;
     }
@@ -896,12 +895,12 @@
     }
 
     /**
-     * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
-     * otherwise.
+     * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+     * temporary motion bounds otherwise.
      */
-    Rect getPossiblyAnimatingBounds() {
-        return mPipBoundsState.getAnimatingBoundsState().isAnimating()
-                ? mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds()
+    Rect getPossiblyMotionBounds() {
+        return mPipBoundsState.getMotionBoundsState().isInMotion()
+                ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
                 : mPipBoundsState.getBounds();
     }
 
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
new file mode 100644
index 0000000..2c6c7b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -0,0 +1,2 @@
+# includes OWNERS from parent directories
+natanieljr@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index e1c9384..0fb43e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -92,6 +92,26 @@
 }
 
 @JvmOverloads
+fun LayersAssertion.appPairsDividerIsVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    end("appPairsDividerIsVisible", bugId, enabled) {
+        this.showsLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.appPairsDividerIsInvisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    end("appPairsDividerIsInVisible", bugId, enabled) {
+        this.hasNotLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+    }
+}
+
+@JvmOverloads
 fun LayersAssertion.dockedStackDividerIsVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 2e6037d..54b8fdc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -130,6 +130,7 @@
         const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
         const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
         const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+        const val APP_PAIRS_DIVIDER = "AppPairDivider"
         const val IMAGE_WALLPAPER = "ImageWallpaper"
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
new file mode 100644
index 0000000..2fc6944
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.apppairs
+
+import android.os.SystemClock
+import android.util.Log
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.compatibility.common.util.SystemUtil
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.TEST_REPETITIONS
+import com.android.wm.shell.flicker.appPairsDividerIsInvisible
+import com.android.wm.shell.flicker.appPairsDividerIsVisible
+import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import java.io.IOException
+
+/**
+ * Test AppPairs launch.
+ * To run this test: `atest WMShellFlickerTests:AppPairsTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AppPairsTest(
+    rotationName: String,
+    rotation: Int
+) : AppPairsTestBase(rotationName, rotation) {
+    private val appPairsSetup: FlickerBuilder
+        get() = FlickerBuilder(instrumentation).apply {
+            val testLaunchActivity = "launch_appPairs_primary_secondary_activities"
+            withTestName {
+                testLaunchActivity
+            }
+            setup {
+                eachRun {
+                    uiDevice.wakeUpAndGoToHomeScreen()
+                    primaryApp.open()
+                    uiDevice.pressHome()
+                    secondaryApp.open()
+                    uiDevice.pressHome()
+                    updateTaskId()
+                }
+            }
+            teardown {
+                eachRun {
+                    executeShellCommand(composePairsCommand(
+                            primaryTaskId, secondaryTaskId, false /* pair */))
+                    primaryApp.exit()
+                    secondaryApp.exit()
+                }
+            }
+            assertions {
+                layersTrace {
+                    navBarLayerIsAlwaysVisible()
+                    statusBarLayerIsAlwaysVisible()
+                }
+                windowManagerTrace {
+                    navBarWindowIsAlwaysVisible()
+                    statusBarWindowIsAlwaysVisible()
+                }
+            }
+        }
+
+    @Test
+    fun testAppPairs_pairPrimaryAndSecondaryApps() {
+        val testTag = "testAppPaired_pairPrimaryAndSecondary"
+        runWithFlicker(appPairsSetup) {
+            withTestName { testTag }
+            repeat {
+                TEST_REPETITIONS
+            }
+            transitions {
+                // TODO pair apps through normal UX flow
+                executeShellCommand(composePairsCommand(
+                        primaryTaskId, secondaryTaskId, true /* pair */))
+                SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+            }
+            assertions {
+                layersTrace {
+                    appPairsDividerIsVisible()
+                    end("appsEndingBounds", enabled = false) {
+                        val entry = this.trace.entries.firstOrNull()
+                                ?: throw IllegalStateException("Trace is empty")
+                        val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+                        this.hasVisibleRegion(primaryApp.defaultWindowName,
+                                appPairsHelper.getPrimaryBounds(dividerRegion))
+                                .and()
+                                .hasVisibleRegion(secondaryApp.defaultWindowName,
+                                        appPairsHelper.getSecondaryBounds(dividerRegion))
+                    }
+                }
+                windowManagerTrace {
+                    end {
+                        showsAppWindow(primaryApp.defaultWindowName)
+                                .and()
+                                .showsAppWindow(secondaryApp.defaultWindowName)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun testAppPairs_unpairPrimaryAndSecondary() {
+        val testTag = "testAppPairs_unpairPrimaryAndSecondary"
+        runWithFlicker(appPairsSetup) {
+            withTestName { testTag }
+            repeat {
+                TEST_REPETITIONS
+            }
+            setup {
+                executeShellCommand(composePairsCommand(
+                        primaryTaskId, secondaryTaskId, true /* pair */))
+                SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+            }
+            transitions {
+                // TODO pair apps through normal UX flow
+                executeShellCommand(composePairsCommand(
+                        primaryTaskId, secondaryTaskId, false /* pair */))
+                SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+            }
+            assertions {
+                layersTrace {
+                    appPairsDividerIsInvisible()
+                    start("appsStartingBounds", enabled = false) {
+                        val entry = this.trace.entries.firstOrNull()
+                                ?: throw IllegalStateException("Trace is empty")
+                        val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+                        this.hasVisibleRegion(primaryApp.defaultWindowName,
+                                appPairsHelper.getPrimaryBounds(dividerRegion))
+                                .and()
+                                .hasVisibleRegion(secondaryApp.defaultWindowName,
+                                        appPairsHelper.getSecondaryBounds(dividerRegion))
+                    }
+                    end("appsEndingBounds", enabled = false) {
+                        this.hasNotLayer(primaryApp.defaultWindowName)
+                                .and()
+                                .hasNotLayer(secondaryApp.defaultWindowName)
+                    }
+                }
+                windowManagerTrace {
+                    end {
+                        hidesAppWindow(primaryApp.defaultWindowName)
+                                .and()
+                                .hidesAppWindow(secondaryApp.defaultWindowName)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun composePairsCommand(
+        primaryApp: String,
+        secondaryApp: String,
+        pair: Boolean
+    ): String = buildString {
+        // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2}
+        append("dumpsys activity service SystemUIService WMShell ")
+        if (pair) {
+            append("pair ")
+        } else {
+            append("unpair ")
+        }
+        append(primaryApp + " " + secondaryApp)
+    }
+
+    private fun executeShellCommand(cmd: String) {
+        try {
+            SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.d("AppPairsTest", "executeShellCommand error!" + e)
+        }
+    }
+
+    private fun updateTaskId() {
+        val primaryAppComponent = primaryApp.openAppIntent.component
+        val secondaryAppComponent = secondaryApp.openAppIntent.component
+        if (primaryAppComponent != null) {
+            primaryTaskId = appPairsHelper.getTaskIdForActivity(
+                    primaryAppComponent.packageName, primaryAppComponent.className).toString()
+        }
+        if (secondaryAppComponent != null) {
+            secondaryTaskId = appPairsHelper.getTaskIdForActivity(
+                    secondaryAppComponent.packageName, secondaryAppComponent.className).toString()
+        }
+    }
+
+    companion object {
+        var primaryTaskId = ""
+        var secondaryTaskId = ""
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
new file mode 100644
index 0000000..1a4de0a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.apppairs
+
+import com.android.wm.shell.flicker.NonRotationTestBase
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
+import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class AppPairsTestBase(
+    rotationName: String,
+    rotation: Int
+) : NonRotationTestBase(rotationName, rotation) {
+    protected val appPairsHelper = AppPairsHelper(instrumentation,
+            TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+            TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+    protected val primaryApp = SplitScreenHelper(instrumentation,
+            TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+            TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+    protected val secondaryApp = SplitScreenHelper(instrumentation,
+            TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
+            TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
new file mode 100644
index 0000000..3b6fcdb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.graphics.Region
+import android.system.helpers.ActivityHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+
+class AppPairsHelper(
+    instrumentation: Instrumentation,
+    activityLabel: String,
+    componentName: ComponentName
+) : BaseAppHelper(
+    instrumentation,
+    activityLabel,
+    componentName
+) {
+    val activityHelper = ActivityHelper.getInstance()
+
+    fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region {
+        val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
+                dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
+        return primaryAppBounds
+    }
+
+    fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region {
+        val displayBounds = WindowUtils.displayBounds
+        val secondaryAppBounds = Region(0,
+                dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
+                displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
+        return secondaryAppBounds
+    }
+
+    fun getTaskIdForActivity(pkgName: String, activityName: String): Int {
+        return activityHelper.getTaskIdForActivity(pkgName, activityName)
+    }
+
+    companion object {
+        const val TEST_REPETITIONS = 1
+        const val TIMEOUT_MS = 3_000L
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 6f008ce..22496a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.helpers
 
-import android.app.ActivityManager
 import android.app.Instrumentation
 import android.content.ComponentName
 import android.content.Context
@@ -46,9 +45,6 @@
     protected val context: Context
         get() = mInstrumentation.context
 
-    private val activityManager: ActivityManager?
-        get() = context.getSystemService(ActivityManager::class.java)
-
     private val appSelector = By.pkg(packageName).depth(0)
 
     protected val isTelevision: Boolean
@@ -77,10 +73,6 @@
         return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
     }
 
-    fun forceStop() {
-        activityManager?.forceStopPackage(packageName)
-    }
-
     override fun getOpenAppIntent(): Intent {
         val intent = Intent()
         intent.component = launcherActivityComponent
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index d343f2a..abb8fc5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -210,4 +210,4 @@
             return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index d1906ba..251bc06 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -53,7 +53,7 @@
     open fun tearDown() {
         if (!isTelevision) return
 
-        testApp.forceStop()
+        testApp.exit()
 
         // Wait for 1 second, and check if the SystemUI has been alive and well since the start.
         SystemClock.sleep(AFTER_TEXT_PROCESS_CHECK_DELAY)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
index 6e10f93..a0056df 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
@@ -60,8 +60,8 @@
             }
             teardown {
                 eachRun {
-                    splitScreenApp.forceStop()
-                    secondaryApp.forceStop()
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
                 }
             }
             assertions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
index 17fc862..32e112d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
@@ -65,8 +65,8 @@
             }
             teardown {
                 eachRun {
-                    splitScreenApp.forceStop()!!
-                    secondaryApp.forceStop()!!
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 7a6e0c1..a65d832 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -50,6 +50,7 @@
     private static final float DEFAULT_ASPECT_RATIO = 1f;
     private static final float MIN_ASPECT_RATIO = 0.5f;
     private static final float MAX_ASPECT_RATIO = 2f;
+    private static final int DEFAULT_MIN_EDGE_SIZE = 100;
 
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private DisplayInfo mDefaultDisplayInfo;
@@ -73,7 +74,8 @@
                 com.android.internal.R.integer.config_defaultPictureInPictureGravity,
                 Gravity.END | Gravity.BOTTOM);
         res.addOverride(
-                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task, 100);
+                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task,
+                DEFAULT_MIN_EDGE_SIZE);
         res.addOverride(
                 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets,
                 "16x16");
@@ -112,6 +114,127 @@
     }
 
     @Test
+    public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
+        final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
+                DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
+                mDefaultDisplayInfo.logicalHeight);
+
+        mPipBoundsState.setOverrideMinSize(null);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        assertEquals(defaultSize, new Size(defaultBounds.width(), defaultBounds.height()));
+        assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+                ASPECT_RATIO_ERROR_MARGIN);
+    }
+
+    @Test
+    public void getDefaultBounds_widerOverrideMinSize_matchesMinSizeWidthAndDefaultAspectRatio() {
+        overrideDefaultAspectRatio(1.0f);
+        // The min size's aspect ratio is greater than the default aspect ratio.
+        final Size overrideMinSize = new Size(150, 120);
+
+        mPipBoundsState.setOverrideMinSize(overrideMinSize);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        // The default aspect ratio should trump the min size aspect ratio.
+        assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+                ASPECT_RATIO_ERROR_MARGIN);
+        // The width of the min size is still used with the default aspect ratio.
+        assertEquals(overrideMinSize.getWidth(), defaultBounds.width());
+    }
+
+    @Test
+    public void getDefaultBounds_tallerOverrideMinSize_matchesMinSizeHeightAndDefaultAspectRatio() {
+        overrideDefaultAspectRatio(1.0f);
+        // The min size's aspect ratio is greater than the default aspect ratio.
+        final Size overrideMinSize = new Size(120, 150);
+
+        mPipBoundsState.setOverrideMinSize(overrideMinSize);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        // The default aspect ratio should trump the min size aspect ratio.
+        assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
+                ASPECT_RATIO_ERROR_MARGIN);
+        // The height of the min size is still used with the default aspect ratio.
+        assertEquals(overrideMinSize.getHeight(), defaultBounds.height());
+    }
+
+    @Test
+    public void getDefaultBounds_imeShowing_offsetByImeHeight() {
+        final int imeHeight = 30;
+        mPipBoundsState.setImeVisibility(false, 0);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        mPipBoundsState.setImeVisibility(true, imeHeight);
+        final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
+
+        assertEquals(imeHeight, defaultBounds.top - defaultBoundsWithIme.top);
+    }
+
+    @Test
+    public void getDefaultBounds_shelfShowing_offsetByShelfHeight() {
+        final int shelfHeight = 30;
+        mPipBoundsState.setShelfVisibility(false, 0);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        mPipBoundsState.setShelfVisibility(true, shelfHeight);
+        final Rect defaultBoundsWithShelf = mPipBoundsAlgorithm.getDefaultBounds();
+
+        assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithShelf.top);
+    }
+
+    @Test
+    public void getDefaultBounds_imeAndShelfShowing_offsetByTallest() {
+        final int imeHeight = 30;
+        final int shelfHeight = 40;
+        mPipBoundsState.setImeVisibility(false, 0);
+        mPipBoundsState.setShelfVisibility(false, 0);
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        mPipBoundsState.setImeVisibility(true, imeHeight);
+        mPipBoundsState.setShelfVisibility(true, shelfHeight);
+        final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
+
+        assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithIme.top);
+    }
+
+    @Test
+    public void getDefaultBounds_boundsAtDefaultGravity() {
+        final Rect insetBounds = new Rect();
+        mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+        overrideDefaultStackGravity(Gravity.END | Gravity.BOTTOM);
+
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        assertEquals(insetBounds.bottom, defaultBounds.bottom);
+        assertEquals(insetBounds.right, defaultBounds.right);
+    }
+
+    @Test
+    public void getNormalBounds_invalidAspectRatio_returnsDefaultBounds() {
+        final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
+
+        // Set an invalid current aspect ratio.
+        mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO / 2);
+        final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
+
+        assertEquals(defaultBounds, normalBounds);
+    }
+
+    @Test
+    public void getNormalBounds_validAspectRatio_returnsAdjustedDefaultBounds() {
+        final Rect defaultBoundsAdjustedToAspectRatio = mPipBoundsAlgorithm.getDefaultBounds();
+        mPipBoundsAlgorithm.transformBoundsToAspectRatio(defaultBoundsAdjustedToAspectRatio,
+                MIN_ASPECT_RATIO, false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
+
+        // Set a valid current aspect ratio different that the default.
+        mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO);
+        final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
+
+        assertEquals(defaultBoundsAdjustedToAspectRatio, normalBounds);
+    }
+
+    @Test
     public void getEntryDestinationBounds_returnBoundsMatchesAspectRatio() {
         final float[] aspectRatios = new float[] {
                 (MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
@@ -121,8 +244,7 @@
         for (float aspectRatio : aspectRatios) {
             mPipBoundsState.setAspectRatio(aspectRatio);
             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
-            final float actualAspectRatio =
-                    destinationBounds.width() / (destinationBounds.height() * 1f);
+            final float actualAspectRatio = getRectAspectRatio(destinationBounds);
             assertEquals("Destination bounds matches the given aspect ratio",
                     aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
         }
@@ -274,6 +396,22 @@
         assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
     }
 
+    private void overrideDefaultAspectRatio(float aspectRatio) {
+        final TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(
+                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+                aspectRatio);
+        mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+    }
+
+    private void overrideDefaultStackGravity(int stackGravity) {
+        final TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(
+                com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+                stackGravity);
+        mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+    }
+
     private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
         final Rect expectedWithMargin = new Rect(expected);
         expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
@@ -282,4 +420,8 @@
                 + " with error margin " + ROUNDING_ERROR_MARGIN,
                 expectedWithMargin.contains(actual));
     }
+
+    private static float getRectAspectRatio(Rect rect) {
+        return rect.width() / (rect.height() * 1f);
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 59e10c1..4bcca06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -135,4 +135,38 @@
 
         verify(callback, never()).accept(true, 100);
     }
+
+    @Test
+    public void testSetOverrideMinSize_changed_callbackInvoked() {
+        final Runnable callback = mock(Runnable.class);
+        mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+        mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
+
+        mPipBoundsState.setOverrideMinSize(new Size(10, 10));
+
+        verify(callback).run();
+    }
+
+    @Test
+    public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
+        final Runnable callback = mock(Runnable.class);
+        mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+        mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
+
+        mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+
+        verify(callback, never()).run();
+    }
+
+    @Test
+    public void testGetOverrideMinEdgeSize() {
+        mPipBoundsState.setOverrideMinSize(null);
+        assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
+
+        mPipBoundsState.setOverrideMinSize(new Size(5, 10));
+        assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+
+        mPipBoundsState.setOverrideMinSize(new Size(15, 10));
+        assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
new file mode 100644
index 0000000..dcee2e1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link PipSnapAlgorithm}. **/
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipSnapAlgorithmTest extends ShellTestCase {
+    private static final int DEFAULT_STASH_OFFSET = 32;
+    private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 2000, 2000);
+    private static final Rect STACK_BOUNDS_CENTERED = new Rect(900, 900, 1100, 1100);
+    private static final Rect MOVEMENT_BOUNDS = new Rect(0, 0,
+            DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width(),
+            DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width());
+
+    private PipSnapAlgorithm mPipSnapAlgorithm;
+
+    @Before
+    public void setUp() {
+        mPipSnapAlgorithm = new PipSnapAlgorithm();
+    }
+
+    @Test
+    public void testApplySnapFraction_topEdge() {
+        final float snapFraction = 0.25f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+        assertEquals(MOVEMENT_BOUNDS.width() / 4, bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.top, bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_rightEdge() {
+        final float snapFraction = 1.5f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+        assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.height() / 2, bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_bottomEdge() {
+        final float snapFraction = 2.25f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+        assertEquals((int) (MOVEMENT_BOUNDS.width() * 0.75f), bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_leftEdge() {
+        final float snapFraction = 3.75f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction);
+
+        assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+        assertEquals((int) (MOVEMENT_BOUNDS.height() * 0.25f), bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_notStashed_isNotOffBounds() {
+        final float snapFraction = 2f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+                PipBoundsState.STASH_TYPE_NONE, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+        assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_stashedLeft() {
+        final float snapFraction = 3f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+                PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+        final int offBoundsWidth = bounds.width() - DEFAULT_STASH_OFFSET;
+        assertEquals(MOVEMENT_BOUNDS.left - offBoundsWidth, bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+    }
+
+    @Test
+    public void testApplySnapFraction_stashedRight() {
+        final float snapFraction = 2f;
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction,
+                PipBoundsState.STASH_TYPE_RIGHT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+        assertEquals(DISPLAY_BOUNDS.right - DEFAULT_STASH_OFFSET, bounds.left);
+        assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+    }
+
+    @Test
+    public void testSnapRectToClosestEdge_rightEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move the centered rect slightly to the right side.
+        bounds.offset(10, 0);
+
+        mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+                PipBoundsState.STASH_TYPE_NONE);
+
+        assertEquals(MOVEMENT_BOUNDS.right, bounds.left);
+    }
+
+    @Test
+    public void testSnapRectToClosestEdge_leftEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move the centered rect slightly to the left side.
+        bounds.offset(-10, 0);
+
+        mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+                PipBoundsState.STASH_TYPE_NONE);
+
+        assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+    }
+
+    @Test
+    public void testSnapRectToClosestEdge_topEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move the centered rect slightly to the top half.
+        bounds.offset(0, -10);
+
+        mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+                PipBoundsState.STASH_TYPE_NONE);
+
+        assertEquals(MOVEMENT_BOUNDS.top, bounds.top);
+    }
+
+    @Test
+    public void testSnapRectToClosestEdge_bottomEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move the centered rect slightly to the bottom half.
+        bounds.offset(0, 10);
+
+        mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+                PipBoundsState.STASH_TYPE_NONE);
+
+        assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top);
+    }
+
+    @Test
+    public void testSnapRectToClosestEdge_stashed_unStahesBounds() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Stash it on the left side.
+        mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, 3.5f,
+                PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS);
+
+        mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds,
+                PipBoundsState.STASH_TYPE_LEFT);
+
+        assertEquals(MOVEMENT_BOUNDS.left, bounds.left);
+    }
+
+    @Test
+    public void testGetSnapFraction_leftEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move it slightly to the left side.
+        bounds.offset(-10, 0);
+
+        final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+        assertEquals(3.5f, snapFraction, 0.1f);
+    }
+
+    @Test
+    public void testGetSnapFraction_rightEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move it slightly to the right side.
+        bounds.offset(10, 0);
+
+        final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+        assertEquals(1.5f, snapFraction, 0.1f);
+    }
+
+    @Test
+    public void testGetSnapFraction_topEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move it slightly to the top half.
+        bounds.offset(0, -10);
+
+        final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+        assertEquals(0.5f, snapFraction, 0.1f);
+    }
+
+    @Test
+    public void testGetSnapFraction_bottomEdge() {
+        final Rect bounds = new Rect(STACK_BOUNDS_CENTERED);
+        // Move it slightly to the bottom half.
+        bounds.offset(0, 10);
+
+        final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS);
+
+        assertEquals(2.5f, snapFraction, 0.1f);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b25c74d..abbc681 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -91,7 +91,7 @@
         mPipBoundsState = new PipBoundsState(mContext);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
         mPipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
-        mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
+        mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
                 mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
                 mFloatingContentCoordinator, mPipUiEventLogger);
@@ -115,7 +115,8 @@
     @Test
     public void updateMovementBounds_minBounds() {
         Rect expectedMinMovementBounds = new Rect();
-        mPipSnapAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, 0);
+        mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds,
+                0);
 
         mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
                 mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
@@ -129,12 +130,13 @@
     public void updateMovementBounds_maxBounds() {
         Point displaySize = new Point();
         mContext.getDisplay().getRealSize(displaySize);
-        Size maxSize = mPipSnapAlgorithm.getSizeForAspectRatio(1,
+        Size maxSize = mPipBoundsAlgorithm.getSizeForAspectRatio(1,
                 mContext.getResources().getDimensionPixelSize(
                         R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y);
         Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight());
         Rect expectedMaxMovementBounds = new Rect();
-        mPipSnapAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, 0);
+        mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds,
+                0);
 
         mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
                 mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 6d626ad..a2964d6 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -94,13 +94,27 @@
 }
 
 static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
-        int weight, int italic) {
+                                      jlong fallbackPtr, int weight, int italic) {
     ScopedLongArrayRO families(env, familyArray);
     std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
-    familyVec.reserve(families.size());
-    for (size_t i = 0; i < families.size(); i++) {
-        FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
-        familyVec.emplace_back(family->family);
+    Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
+    if (typeface != nullptr) {
+        const std::vector<std::shared_ptr<minikin::FontFamily>>& fallbackFamilies =
+            toTypeface(fallbackPtr)->fFontCollection->getFamilies();
+        familyVec.reserve(families.size() + fallbackFamilies.size());
+        for (size_t i = 0; i < families.size(); i++) {
+            FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+            familyVec.emplace_back(family->family);
+        }
+        for (size_t i = 0; i < fallbackFamilies.size(); i++) {
+            familyVec.emplace_back(fallbackFamilies[i]);
+        }
+    } else {
+        familyVec.reserve(families.size());
+        for (size_t i = 0; i < families.size(); i++) {
+            FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+            familyVec.emplace_back(family->family);
+        }
     }
     return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
 }
@@ -250,7 +264,7 @@
     { "nativeGetReleaseFunc",     "()J",  (void*)Typeface_getReleaseFunc },
     { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
     { "nativeGetWeight",      "(J)I",  (void*)Typeface_getWeight },
-    { "nativeCreateFromArray",    "([JII)J",
+    { "nativeCreateFromArray",    "([JJII)J",
                                            (void*)Typeface_createFromArray },
     { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
     { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index e023aa1..9dbdede 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -150,8 +150,8 @@
         return mListenerId;
     }
 
-    /** Returns true if this represents a system identity. */
-    public boolean isSystem() {
+    /** Returns true if this represents a system server identity. */
+    public boolean isSystemServer() {
         return mUid == Process.SYSTEM_UID;
     }
 
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index 5f79dc5..2217eb3 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.filter.Filter;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -115,9 +116,9 @@
      * keys for different purposes.
      *
      * @param keyToken the token to be used to link the key slot. Use {@link Tuner.INVALID_KEYTOKEN}
-     *        to remove the to remove the current key from descrambler. If the current keyToken
-     *        comes from MediaCas session, use {@link Tuner.INVALID_KEYTOKEN} to remove current key
-     *        before close MediaCas session.
+     *        to remove the current key from descrambler. If the current keyToken comes from a
+     *        MediaCas session, use {@link Tuner.INVALID_KEYTOKEN} to remove current key before
+     *        closing the MediaCas session.
      * @return result status of the operation.
      */
     @Result
@@ -125,6 +126,9 @@
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             Objects.requireNonNull(keyToken, "key token must not be null");
+            if (!isValidKeyToken(keyToken)) {
+                return Tuner.RESULT_INVALID_ARGUMENT;
+            }
             return nativeSetKeyToken(keyToken);
         }
     }
@@ -147,4 +151,17 @@
         }
     }
 
+    private boolean isValidKeyToken(byte[] keyToken) {
+        if (keyToken.length == 0 || keyToken.length > 16) {
+            Log.d(TAG, "Invalid key token size: " + (keyToken.length * 8) + " bit.");
+            return false;
+        }
+        for (int i = 0; i < keyToken.length; i++) {
+            if (keyToken[i] < 0) {
+                Log.d(TAG, "Invalid key token.");
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 27b33ac..a9da772 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -878,11 +878,14 @@
     }
 
     /**
-     * Connects Conditional Access Modules (CAM) through Common Interface (CI)
+     * Connects Conditional Access Modules (CAM) through Common Interface (CI).
      *
      * <p>The demux uses the output from the frontend as the input by default, and must change to
      * use the output from CI-CAM as the input after this call.
      *
+     * <p> Note that this API is used to connect the CI-CAM to the Demux module while
+     * {@link connectFrontendToCiCam(int)} is used to connect CI-CAM to the Frontend module.
+     *
      * @param ciCamId specify CI-CAM Id to connect.
      * @return result status of the operation.
      */
@@ -895,23 +898,30 @@
     }
 
     /**
-     * Link Conditional Access Modules (CAM) Frontend to support Common Interface (CI) by-pass mode.
+     * Connect Conditional Access Modules (CAM) Frontend to support Common Interface (CI)
+     * by-pass mode.
      *
      * <p>It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
      * the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
      * the TS directly from the frontend.
      *
-     * <p>Use {@link #unlinkFrontendToCicam(int)} to disconnect.
+     * <p> Note that this API is used to connect the CI-CAM to the Frontend module while
+     * {@link connectCiCam(int)} is used to connect CI-CAM to the Demux module.
+     *
+     * <p>Use {@link #disconnectFrontendToCiCam(int)} to disconnect.
      *
      * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
      * no-op and return {@link INVALID_LTS_ID}. Use {@link TunerVersionChecker.getTunerVersion()} to
      * check the version.
      *
-     * @param ciCamId specify CI-CAM Id to link.
+     * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
+     *                Common Interface (CI), to link.
      * @return Local transport stream id when connection is successfully established. Failed
-     *         operation returns {@link INVALID_LTS_ID}.
+     *         operation returns {@link INVALID_LTS_ID} while unsupported version also returns
+     *         {@link INVALID_LTS_ID}. Check the current HAL version using
+     *         {@link TunerVersionChecker.getTunerVersion()}.
      */
-    public int linkFrontendToCiCam(int ciCamId) {
+    public int connectFrontendToCiCam(int ciCamId) {
         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
                 "linkFrontendToCiCam")) {
             if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
@@ -922,10 +932,13 @@
     }
 
     /**
-     * Disconnects Conditional Access Modules (CAM)
+     * Disconnects Conditional Access Modules (CAM).
      *
      * <p>The demux will use the output from the frontend as the input after this call.
      *
+     * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
+     * {@link disconnectFrontendToCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
+     *
      * @return result status of the operation.
      */
     @Result
@@ -937,18 +950,23 @@
     }
 
     /**
-     * Unlink Conditional Access Modules (CAM) Frontend.
+     * Disconnect Conditional Access Modules (CAM) Frontend.
      *
      * <p>It is used by the client to unlink CI-CAM to a Frontend.
      *
+     * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
+     * {@link disconnectCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
+     *
      * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
      * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
      *
-     * @param ciCamId specify CI-CAM Id to unlink.
-     * @return result status of the operation.
+     * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
+     *                Common Interface (CI), to disconnect.
+     * @return result status of the operation. Unsupported version would return
+     *         {@link RESULT_UNAVAILABLE}
      */
     @Result
-    public int unlinkFrontendToCiCam(int ciCamId) {
+    public int disconnectFrontendToCiCam(int ciCamId) {
         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
                 "unlinkFrontendToCiCam")) {
             if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 82cc78d..451f54c 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -186,7 +186,7 @@
             value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
                     SCRAMBLING_STATUS_SCRAMBLED})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ScramblingStatusMask {}
+    public @interface ScramblingStatus {}
 
     /**
      * Content’s scrambling status is unknown
@@ -204,6 +204,23 @@
     public static final int SCRAMBLING_STATUS_SCRAMBLED =
             android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.SCRAMBLED;
 
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MONITOR_EVENT_",
+            value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MonitorEventTypeMask {}
+
+    /**
+     * Monitor scrambling status change.
+     */
+    public static final int MONITOR_EVENT_SCRAMBLING_STATUS =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.SCRAMBLING_STATUS;
+    /**
+     * Monitor ip cid change.
+     */
+    public static final int MONITOR_EVENT_IP_CID_CHANGE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.IP_CID_CHANGE;
 
     private static final String TAG = "Filter";
 
@@ -222,7 +239,7 @@
             int type, int subType, FilterConfiguration settings);
     private native int nativeGetId();
     private native long nativeGetId64Bit();
-    private native int nativeconfigureScramblingEvent(int scramblingStatusMask);
+    private native int nativeConfigureMonitorEvent(int monitorEventTypesMask);
     private native int nativeSetDataSource(Filter source);
     private native int nativeStartFilter();
     private native int nativeStopFilter();
@@ -306,34 +323,40 @@
     }
 
     /**
-     * Configure the Filter to monitor specific Scrambling Status through
-     * {@link ScramblingStatusEvent}.
+     * Configure the Filter to monitor scrambling status and ip cid change. Set corresponding bit of
+     * {@link MonitorEventTypeMask} to monitor the change. Reset to stop monitoring.
      *
      * <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios:
-     *
      * <ul>
-     *   <li>When this method is called, the first detected scrambling status should be sent.
-     *   <li>When the filter transits into the monitored statuses configured in
-     *       {@code scramblingStatusMask}, event should be sent.
+     *   <li>When this method is called with {@link MONITOR_EVENT_SCRAMBLING_STATUS}, the first
+     *       detected scrambling status should be sent.
+     *   <li>When the Scrambling status transits into different status, event should be sent.
+     *     <ul/>
+     *
+     * <p>{@link IpCidChangeEvent} should be sent at the following two scenarios:
+     * <ul>
+     *   <li>When this method is called with {@link MONITOR_EVENT_IP_CID_CHANGE}, the first detected
+     *       CID for the IP should be sent.
+     *   <li>When the CID is changed to different value for the IP filter, event should be sent.
      *     <ul/>
      *
      * <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version
      * will cause no-op. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
      * information.
      *
-     * @param scramblingStatusMask Scrambling Statuses to be monitored. Set corresponding bit to
-     *                             monitor it. Reset to stop monitoring.
+     * @param monitorEventTypesMask Types of event to be monitored. Set corresponding bit to
+     *                              monitor it. Reset to stop monitoring.
      * @return result status of the operation.
      */
     @Result
-    public int configureScramblingStatusEvent(@ScramblingStatusMask int scramblingStatusMask) {
+    public int configureMonitorEvent(@MonitorEventTypeMask int monitorEventTypesMask) {
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
-                    TunerVersionChecker.TUNER_VERSION_1_1, "configureScramblingStatusEvent")) {
+                    TunerVersionChecker.TUNER_VERSION_1_1, "configureMonitorEvent")) {
                 return Tuner.RESULT_UNAVAILABLE;
             }
-            return nativeconfigureScramblingEvent(scramblingStatusMask);
+            return nativeConfigureMonitorEvent(monitorEventTypesMask);
         }
     }
 
diff --git a/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java b/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java
new file mode 100644
index 0000000..c0043f3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/IpCidChangeEvent.java
@@ -0,0 +1,46 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Ip Cid Change event sent from {@link Filter} objects new ip cid.
+ *
+ * <p>This event is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+ *
+ * @hide
+ */
+@SystemApi
+public final class IpCidChangeEvent extends FilterEvent {
+    private final int mCid;
+
+    private IpCidChangeEvent(int cid) {
+        mCid = cid;
+    }
+
+    /**
+     * Gets ip cid.
+     *
+     * <p>This event is only sent in Tuner 1.1 or higher version. Use
+     * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+     */
+    public int getIpCid() {
+        return mCid;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 52ce208..91992af 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -51,7 +51,7 @@
     public @interface TsIndexMask {}
 
     /**
-     * Invalid TS index.
+     * Invalid Transport Stream (TS) index.
      */
     public static final int TS_INDEX_INVALID = 0;
     /**
diff --git a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
index a78b96e..fef5396 100644
--- a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
+++ b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
@@ -30,18 +30,17 @@
 public final class ScramblingStatusEvent extends FilterEvent {
     private final int mScramblingStatus;
 
-    private ScramblingStatusEvent(@Filter.ScramblingStatusMask int scramblingStatus) {
+    private ScramblingStatusEvent(@Filter.ScramblingStatus int scramblingStatus) {
         mScramblingStatus = scramblingStatus;
     }
 
     /**
      * Gets Scrambling Status Type.
      *
-     * <p>This event field is only sent in Tuner 1.1 or higher version. Unsupported version returns
-     * default value 0. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
-     * information.
+     * <p>This event field is only sent in Tuner 1.1 or higher version. Use
+     * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
      */
-    @Filter.ScramblingStatusMask
+    @Filter.ScramblingStatus
     public int getScramblingStatus() {
         return mScramblingStatus;
     }
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index ed56b43..798bf6e 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -19,8 +19,7 @@
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderClient;
-import android.content.ContentUris;
-import android.content.ContentValues;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +31,6 @@
 import android.media.ThumbnailUtils;
 import android.net.Uri;
 import android.os.BatteryManager;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.storage.StorageVolume;
 import android.provider.MediaStore;
@@ -103,8 +101,6 @@
     private MtpStorageManager mManager;
 
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
-    private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
-    private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
     private static final String NO_MEDIA = ".nomedia";
 
     static {
@@ -431,7 +427,7 @@
         }
         // Add the new file to MediaProvider
         if (succeeded) {
-            MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+            updateMediaStore(mContext, obj.getPath().toFile());
         }
     }
 
@@ -580,32 +576,8 @@
             return MtpConstants.RESPONSE_GENERAL_ERROR;
         }
 
-        // finally update MediaProvider
-        ContentValues values = new ContentValues();
-        values.put(Files.FileColumns.DATA, newPath.toString());
-        String[] whereArgs = new String[]{oldPath.toString()};
-        try {
-            // note - we are relying on a special case in MediaProvider.update() to update
-            // the paths for all children in the case where this is a directory.
-            final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-            mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
-        }
-
-        // check if nomedia status changed
-        if (obj.isDir()) {
-            // for directories, check if renamed from something hidden to something non-hidden
-            if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
-                MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
-            }
-        } else {
-            // for files, check if renamed from .nomedia to something else
-            if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
-                    && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
-                MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
-            }
-        }
+        updateMediaStore(mContext, oldPath.toFile());
+        updateMediaStore(mContext, newPath.toFile());
         return MtpConstants.RESPONSE_OK;
     }
 
@@ -635,48 +607,15 @@
             Log.e(TAG, "Failed to end move object");
             return;
         }
-
         obj = mManager.getObject(objId);
         if (!success || obj == null)
             return;
-        // Get parent info from MediaProvider, since the id is different from MTP's
-        ContentValues values = new ContentValues();
+
         Path path = newParentObj.getPath().resolve(name);
         Path oldPath = oldParentObj.getPath().resolve(name);
-        values.put(Files.FileColumns.DATA, path.toString());
-        if (obj.getParent().isRoot()) {
-            values.put(Files.FileColumns.PARENT, 0);
-        } else {
-            int parentId = findInMedia(newParentObj, path.getParent());
-            if (parentId != -1) {
-                values.put(Files.FileColumns.PARENT, parentId);
-            } else {
-                // The new parent isn't in MediaProvider, so delete the object instead
-                deleteFromMedia(obj, oldPath, obj.isDir());
-                return;
-            }
-        }
-        // update MediaProvider
-        Cursor c = null;
-        String[] whereArgs = new String[]{oldPath.toString()};
-        try {
-            int parentId = -1;
-            if (!oldParentObj.isRoot()) {
-                parentId = findInMedia(oldParentObj, oldPath.getParent());
-            }
-            if (oldParentObj.isRoot() || parentId != -1) {
-                // Old parent exists in MediaProvider - perform a move
-                // note - we are relying on a special case in MediaProvider.update() to update
-                // the paths for all children in the case where this is a directory.
-                final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-                mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
-            } else {
-                // Old parent doesn't exist - add the object
-                MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
-        }
+
+        updateMediaStore(mContext, oldPath.toFile());
+        updateMediaStore(mContext, path.toFile());
     }
 
     @VisibleForNative
@@ -699,7 +638,19 @@
         if (!success) {
             return;
         }
-        MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+
+        updateMediaStore(mContext, obj.getPath().toFile());
+    }
+
+    private static void updateMediaStore(@NonNull Context context, @NonNull File file) {
+        final ContentResolver resolver = context.getContentResolver();
+        // For file, check whether the file name is .nomedia or not.
+        // If yes, scan the parent directory to update all files in the directory.
+        if (!file.isDirectory() && file.getName().toLowerCase(Locale.ROOT).endsWith(NO_MEDIA)) {
+            MediaStore.scanFile(resolver, file.getParentFile());
+        } else {
+            MediaStore.scanFile(resolver, file);
+        }
     }
 
     @VisibleForNative
@@ -928,26 +879,6 @@
             deleteFromMedia(obj, obj.getPath(), obj.isDir());
     }
 
-    private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
-        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-
-        int ret = -1;
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
-                    new String[]{path.toString()}, null, null);
-            if (c != null && c.moveToNext()) {
-                ret = c.getInt(0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error finding " + path + " in MediaProvider");
-        } finally {
-            if (c != null)
-                c.close();
-        }
-        return ret;
-    }
-
     private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
         final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
         try {
@@ -963,13 +894,10 @@
             }
 
             String[] whereArgs = new String[]{path.toString()};
-            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
-                if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
-                    MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
-                }
-            } else {
-                Log.i(TAG, "Mediaprovider didn't delete " + path);
+            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) == 0) {
+                Log.i(TAG, "MediaProvider didn't delete " + path);
             }
+            updateMediaStore(mContext, path.toFile());
         } catch (Exception e) {
             Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index b255a48..72dc32e 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -604,11 +604,16 @@
 
         jlong byteNumber = static_cast<jlong>(tsRecordEvent.byteNumber);
 
-        jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].tsRecord().pts)
-                : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
-        jlong firstMbInSlice = (eventsExt.size() > i)
-                ? static_cast<jint>(eventsExt[i].tsRecord().firstMbInSlice)
-                : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+        jlong pts;
+        jlong firstMbInSlice;
+        if (eventsExt.size() > i && eventsExt[i].getDiscriminator() ==
+                    DemuxFilterEventExt::Event::hidl_discriminator::tsRecord) {
+            pts = static_cast<jlong>(eventsExt[i].tsRecord().pts);
+            firstMbInSlice = static_cast<jint>(eventsExt[i].tsRecord().firstMbInSlice);
+        } else {
+            pts = static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+            firstMbInSlice = static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+        }
 
         jobject obj =
                 env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber,
@@ -632,16 +637,25 @@
 
         jint scHevcIndexMask = static_cast<jint>(mmtpRecordEvent.scHevcIndexMask);
         jlong byteNumber = static_cast<jlong>(mmtpRecordEvent.byteNumber);
-        jint mpuSequenceNumber = (eventsExt.size() > i)
-                ? static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber)
-                : static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
-        jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].mmtpRecord().pts)
-                : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
-        jlong firstMbInSlice = (eventsExt.size() > i)
-                ? static_cast<jint>(eventsExt[i].mmtpRecord().firstMbInSlice)
-                : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
-        jlong tsIndexMask = (eventsExt.size() > i)
-                ? static_cast<jint>(eventsExt[i].mmtpRecord().tsIndexMask) : 0;
+
+        jint mpuSequenceNumber;
+        jlong pts;
+        jlong firstMbInSlice;
+        jlong tsIndexMask;
+
+        if (eventsExt.size() > i && eventsExt[i].getDiscriminator() ==
+                    DemuxFilterEventExt::Event::hidl_discriminator::mmtpRecord) {
+            mpuSequenceNumber = static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber);
+            pts = static_cast<jlong>(eventsExt[i].mmtpRecord().pts);
+            firstMbInSlice = static_cast<jint>(eventsExt[i].mmtpRecord().firstMbInSlice);
+            tsIndexMask = static_cast<jint>(eventsExt[i].mmtpRecord().tsIndexMask);
+        } else {
+            mpuSequenceNumber =
+                    static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
+            pts = static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+            firstMbInSlice = static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
+            tsIndexMask = 0;
+        }
 
         jobject obj =
                 env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
@@ -720,11 +734,21 @@
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/ScramblingStatusEvent");
     jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
-    for (int i = 0; i < eventsExt.size(); i++) {
-        auto scramblingStatus = eventsExt[i].scramblingStatus();
-        jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(scramblingStatus));
-        env->SetObjectArrayElement(arr, i, obj);
-    }
+    auto scramblingStatus = eventsExt[0].monitorEvent().scramblingStatus();
+    jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(scramblingStatus));
+    env->SetObjectArrayElement(arr, 0, obj);
+    return arr;
+}
+
+jobjectArray FilterCallback::getIpCidChangeEvent(
+        jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/IpCidChangeEvent");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
+
+    auto cid = eventsExt[0].monitorEvent().cid();
+    jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(cid));
+    env->SetObjectArrayElement(arr, 0, obj);
     return arr;
 }
 
@@ -734,11 +758,9 @@
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/RestartEvent");
     jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(I)V");
 
-    for (int i = 0; i < eventsExt.size(); i++) {
-        auto startId = eventsExt[i].startId();
-        jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(startId));
-        env->SetObjectArrayElement(arr, i, obj);
-    }
+    auto startId = eventsExt[0].startId();
+    jobject obj = env->NewObject(eventClazz, eventInit, static_cast<jint>(startId));
+    env->SetObjectArrayElement(arr, 0, obj);
     return arr;
 }
 
@@ -747,17 +769,31 @@
     ALOGD("FilterCallback::onFilterEvent_1_1");
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jobjectArray array;
 
     std::vector<DemuxFilterEvent::Event> events = filterEvent.events;
     std::vector<DemuxFilterEventExt::Event> eventsExt = filterEventExt.events;
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
-    jobjectArray array = env->NewObjectArray(events.size(), eventClazz, NULL);
 
     if (events.empty() && !eventsExt.empty()) {
+        // Monitor event should be sent with one DemuxFilterMonitorEvent in DemuxFilterEventExt.
+        array = env->NewObjectArray(1, eventClazz, NULL);
         auto eventExt = eventsExt[0];
         switch (eventExt.getDiscriminator()) {
-            case DemuxFilterEventExt::Event::hidl_discriminator::scramblingStatus: {
-                array = getScramblingStatusEvent(array, eventsExt);
+            case DemuxFilterEventExt::Event::hidl_discriminator::monitorEvent: {
+                switch (eventExt.monitorEvent().getDiscriminator()) {
+                    case DemuxFilterMonitorEvent::hidl_discriminator::scramblingStatus: {
+                        array = getScramblingStatusEvent(array, eventsExt);
+                        break;
+                    }
+                    case DemuxFilterMonitorEvent::hidl_discriminator::cid: {
+                        array = getIpCidChangeEvent(array, eventsExt);
+                        break;
+                    }
+                    default: {
+                        break;
+                    }
+                }
                 break;
             }
             case DemuxFilterEventExt::Event::hidl_discriminator::startId: {
@@ -771,6 +807,7 @@
     }
 
     if (!events.empty()) {
+        array = env->NewObjectArray(events.size(), eventClazz, NULL);
         auto event = events[0];
         switch (event.getDiscriminator()) {
             case DemuxFilterEvent::Event::hidl_discriminator::media: {
@@ -4069,8 +4106,8 @@
                     ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
 }
 
-static jint android_media_tv_Tuner_configure_scrambling_status_event(
-        JNIEnv* env, jobject filter, int scramblingStatusMask) {
+static jint android_media_tv_Tuner_configure_monitor_event(
+        JNIEnv* env, jobject filter, int monitorEventType) {
     sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
     if (iFilterSp == NULL) {
         ALOGD("Failed to configure scrambling event: filter not found");
@@ -4082,7 +4119,7 @@
     Result res;
 
     if (iFilterSp_1_1 != NULL) {
-        res = iFilterSp_1_1->configureScramblingEvent(scramblingStatusMask);
+        res = iFilterSp_1_1->configureMonitorEvent(monitorEventType);
     } else {
         ALOGW("configureScramblingEvent is not supported with the current HAL implementation.");
         return (jint) Result::INVALID_STATE;
@@ -4762,8 +4799,8 @@
     { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
     { "nativeGetId64Bit", "()J",
             (void *)android_media_tv_Tuner_get_filter_64bit_id },
-    { "nativeconfigureScramblingEvent", "(I)I",
-            (void *)android_media_tv_Tuner_configure_scrambling_status_event },
+    { "nativeConfigureMonitorEvent", "(I)I",
+            (void *)android_media_tv_Tuner_configure_monitor_event },
     { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_set_filter_data_source },
     { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index ebb95f4..e79b5c2 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -44,7 +44,6 @@
 using ::android::hardware::hidl_vec;
 using ::android::hardware::kSynchronizedReadWrite;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
-using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
 using ::android::hardware::tv::tuner::V1_0::DemuxPid;
@@ -73,6 +72,8 @@
 using ::android::hardware::tv::tuner::V1_0::PlaybackStatus;
 using ::android::hardware::tv::tuner::V1_0::RecordStatus;
 using ::android::hardware::tv::tuner::V1_0::Result;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
 using ::android::hardware::tv::tuner::V1_1::FrontendScanMessageExt1_1;
 using ::android::hardware::tv::tuner::V1_1::FrontendScanMessageTypeExt1_1;
 using ::android::hardware::tv::tuner::V1_1::IFrontendCallback;
@@ -188,6 +189,8 @@
             jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
     jobjectArray getScramblingStatusEvent(
             jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
+    jobjectArray getIpCidChangeEvent(
+            jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
     jobjectArray getRestartEvent(
             jobjectArray& arr, const std::vector<DemuxFilterEventExt::Event>& eventsExt);
 };
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index d134b37..eca67bd 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -151,6 +151,7 @@
     AHardwareBuffer_allocate; # introduced=26
     AHardwareBuffer_describe; # introduced=26
     AHardwareBuffer_fromHardwareBuffer; # introduced=26
+    AHardwareBuffer_getId; # introduced=31
     AHardwareBuffer_getNativeHandle; # introduced=26
     AHardwareBuffer_isSupported; # introduced=29
     AHardwareBuffer_lock; # introduced=26
diff --git a/packages/CompanionDeviceManager/res/layout/buttons.xml b/packages/CompanionDeviceManager/res/layout/buttons.xml
index b190a7f..a80720c 100644
--- a/packages/CompanionDeviceManager/res/layout/buttons.xml
+++ b/packages/CompanionDeviceManager/res/layout/buttons.xml
@@ -29,14 +29,15 @@
         android:id="@+id/button_cancel"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="@android:string/cancel"
+        android:text="@string/consent_no"
+        android:textColor="?android:attr/textColorSecondary"
         style="@android:style/Widget.Material.Button.Borderless.Colored"
     />
     <Button
         android:id="@+id/button_pair"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="@android:string/ok"
+        android:text="@string/consent_yes"
         style="@android:style/Widget.Material.Button.Borderless.Colored"
     />
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
index db014ae..273347a 100644
--- a/packages/CompanionDeviceManager/res/layout/device_chooser.xml
+++ b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
@@ -23,11 +23,13 @@
 
     <include layout="@layout/title" />
 
+    <include layout="@layout/profile_summary" />
+
     <ListView
         android:id="@+id/device_list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_below="@+id/title"
+        android:layout_below="@+id/profile_summary"
         android:layout_above="@+id/buttons"
         style="@android:style/Widget.Material.ListView"
     />
diff --git a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
index 7cde41a..1336e79 100644
--- a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
@@ -23,6 +23,8 @@
 
     <include layout="@layout/title" />
 
+    <include layout="@layout/profile_summary" />
+
     <include layout="@layout/buttons" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/profile_summary.xml b/packages/CompanionDeviceManager/res/layout/profile_summary.xml
new file mode 100644
index 0000000..80fec59
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/profile_summary.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/profile_summary"
+    android:layout_below="@+id/title"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:textColor="?android:attr/textColorSecondary"
+    android:textSize="14sp"
+    android:gravity="center"
+/>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/title.xml b/packages/CompanionDeviceManager/res/layout/title.xml
index 0a44fbb..9a50366 100644
--- a/packages/CompanionDeviceManager/res/layout/title.xml
+++ b/packages/CompanionDeviceManager/res/layout/title.xml
@@ -20,5 +20,6 @@
     android:id="@+id/title"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:gravity="center"
     style="@*android:style/TextAppearance.Widget.Toolbar.Title"
 />
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index c4372eb..1b96b00 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,9 +20,21 @@
     <string name="app_label">Companion Device Manager</string>
 
     <!-- Title of the device selection dialog. -->
-    <string name="chooser_title">Link with &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt;</string>
+    <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
 
-    <!-- Title of the device pairing confirmation dialog. -->
-    <string name="confirmation_title">Link &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; with &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <!-- The generic placeholder for a device type when nothing specific is known about it [CHAR LIMIT=30] -->
+    <string name="profile_name_generic">device</string>
+
+    <!-- Title of the device association confirmation dialog. -->
+    <string name="confirmation_title">Set &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage your <xliff:g id="profile_name" example="watch">%2$s</xliff:g> - &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%3$s</xliff:g>&lt;/strong&gt;</string>
+
+    <!-- Text of the device profile permissions explanation in the association dialog. -->
+    <string name="profile_summary"><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g> is needed to manage your <xliff:g id="profile_name" example="watch">%2$s</xliff:g>. <xliff:g id="app_name2" example="Android Wear">%3$s</xliff:g> will get access to <xliff:g id="permissions" example="Notifications, Calendar and Phone">%4$s</xliff:g> while the <xliff:g id="profile_name2" example="watch">%5$s</xliff:g> is connected.</string>
+
+    <!-- Positive button for the device-app association consent dialog [CHAR LIMIT=30] -->
+    <string name="consent_yes">Yes</string>
+
+    <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
+    <string name="consent_no">No thanks</string>
 
 </resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index bdfbf82..f42a51d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -17,11 +17,13 @@
 package com.android.companiondevicemanager;
 
 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+import static android.text.TextUtils.withoutPrefix;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import static java.util.Objects.requireNonNull;
 
 import android.app.Activity;
+import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -62,12 +64,19 @@
 
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
-        if (getService().mRequest.isSingleDevice()) {
+        String deviceProfile = getRequest().getDeviceProfile();
+        String profileName = deviceProfile == null
+                ? getString(R.string.profile_name_generic)
+                //TODO introduce PermissionController APIs to resolve UI values
+                : withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile).toLowerCase();
+
+        if (getRequest().isSingleDevice()) {
             setContentView(R.layout.device_confirmation);
             final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
             setTitle(Html.fromHtml(getString(
                     R.string.confirmation_title,
                     getCallingAppName(),
+                    profileName,
                     selectedDevice.getDisplayName()), 0));
             mPairButton = findViewById(R.id.button_pair);
             mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
@@ -77,7 +86,9 @@
             setContentView(R.layout.device_chooser);
             mPairButton = findViewById(R.id.button_pair);
             mPairButton.setVisibility(View.GONE);
-            setTitle(Html.fromHtml(getString(R.string.chooser_title, getCallingAppName()), 0));
+            setTitle(Html.fromHtml(getString(R.string.chooser_title,
+                    profileName,
+                    getCallingAppName()), 0));
             mDeviceListView = findViewById(R.id.device_list);
             final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter;
             mDeviceListView.setAdapter(adapter);
@@ -97,12 +108,33 @@
             });
             mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false);
         }
+
+        TextView profileSummary = findViewById(R.id.profile_summary);
+
+        if (deviceProfile != null) {
+            //TODO introduce PermissionController APIs to resolve UI values
+            String privileges = "Notifications, Phone, Contacts and Calendar";
+            profileSummary.setVisibility(View.VISIBLE);
+            profileSummary.setText(getString(R.string.profile_summary,
+                    getCallingAppName(),
+                    profileName,
+                    getCallingAppName(),
+                    privileges,
+                    profileName));
+        } else {
+            profileSummary.setVisibility(View.GONE);
+        }
+
         getService().mActivity = this;
 
         mCancelButton = findViewById(R.id.button_cancel);
         mCancelButton.setOnClickListener(v -> cancel());
     }
 
+    private AssociationRequest getRequest() {
+        return getService().mRequest;
+    }
+
     private void cancel() {
         getService().onCancel();
         setResult(RESULT_CANCELED);
@@ -132,7 +164,7 @@
 
     @Override
     public String getCallingPackage() {
-        return requireNonNull(getService().mRequest.getCallingPackage());
+        return requireNonNull(getRequest().getCallingPackage());
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index a7e397e..2fe351e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -64,7 +64,7 @@
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
 import android.widget.TextView;
 
 import com.android.internal.infra.AndroidFuture;
@@ -329,7 +329,7 @@
         mServiceCallback.cancel(true);
     }
 
-    class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
+    class DevicesAdapter extends BaseAdapter {
         private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
         private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
 
@@ -341,10 +341,6 @@
             return icon;
         }
 
-        public DevicesAdapter() {
-            super(DeviceDiscoveryService.this, 0, mDevicesFound);
-        }
-
         @Override
         public View getView(
                 int position,
@@ -391,6 +387,21 @@
             mColors.put(colorAttr, result);
             return result;
         }
+
+        @Override
+        public int getCount() {
+            return mDevicesFound.size();
+        }
+
+        @Override
+        public DeviceFilterPair getItem(int position) {
+            return mDevicesFound.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
     }
 
     /**
diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml
index fcaf321..1697097 100644
--- a/packages/InputDevices/res/values-mn/strings.xml
+++ b/packages/InputDevices/res/values-mn/strings.xml
@@ -22,9 +22,9 @@
     <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгар хэл, Авиа зүй"</string>
     <string name="keyboard_layout_italian" msgid="6497079660449781213">"Итали"</string>
     <string name="keyboard_layout_danish" msgid="8036432066627127851">"Дани"</string>
-    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвеги"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
     <string name="keyboard_layout_swedish" msgid="732959109088479351">"Швед"</string>
-    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Финлянд"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Финланд"</string>
     <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Хорват"</string>
     <string name="keyboard_layout_czech" msgid="1349256901452975343">"Чех"</string>
     <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Чех хэлний QWERTY загвар"</string>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3af4b25..64dc2af 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -51,6 +51,7 @@
         "SettingsLibRadioButtonPreference",
         "SettingsLibDisplayDensityUtils",
         "SettingsLibUtils",
+        "SettingsLibEmergencyNumber",
         "SettingsLibTopIntroPreference",
     ],
 }
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
new file mode 100644
index 0000000..3c41f78
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -0,0 +1,10 @@
+android_library {
+    name: "SettingsLibEmergencyNumber",
+
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml b/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml
new file mode 100644
index 0000000..b877fc4
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.emergencynumber">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
new file mode 100644
index 0000000..12d21ca
--- /dev/null
+++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.emergencynumber;
+
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Util class to help manage emergency numbers
+ */
+public class EmergencyNumberUtils {
+    private static final String TAG = "EmergencyNumberUtils";
+    private static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number";
+    @VisibleForTesting
+    static final String FALL_BACK_NUMBER = "112";
+
+    private final Context mContext;
+    private final TelephonyManager mTelephonyManager;
+
+
+    public EmergencyNumberUtils(Context context) {
+        mContext = context;
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        } else {
+            mTelephonyManager = null;
+        }
+    }
+
+    /**
+     * Returns the most appropriate number for police.
+     */
+    public String getDefaultPoliceNumber() {
+        if (mTelephonyManager == null) {
+            return FALL_BACK_NUMBER;
+        }
+        final List<EmergencyNumber> promotedPoliceNumber = getPromotedEmergencyNumbers(
+                EMERGENCY_SERVICE_CATEGORY_POLICE);
+        if (promotedPoliceNumber == null || promotedPoliceNumber.isEmpty()) {
+            return FALL_BACK_NUMBER;
+        }
+        return promotedPoliceNumber.get(0).getNumber();
+    }
+
+    /**
+     * Returns the number chosen by user. If user has not provided any number, use default ({@link
+     * #getDefaultPoliceNumber()}).
+     */
+    public String getPoliceNumber() {
+        final String userProvidedNumber = Settings.Secure.getString(mContext.getContentResolver(),
+                EMERGENCY_GESTURE_CALL_NUMBER);
+        return TextUtils.isEmpty(userProvidedNumber)
+                ? getDefaultPoliceNumber() : userProvidedNumber;
+    }
+
+    private List<EmergencyNumber> getPromotedEmergencyNumbers(int categories) {
+        Map<Integer, List<EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList(
+                categories);
+        if (allLists == null || allLists.isEmpty()) {
+            Log.w(TAG, "Unable to retrieve emergency number lists!");
+            return new ArrayList<>();
+        }
+        Map<Integer, List<EmergencyNumber>> promotedEmergencyNumberLists = new ArrayMap<>();
+        for (Map.Entry<Integer, List<EmergencyNumber>> entry : allLists.entrySet()) {
+            if (entry.getKey() == null || entry.getValue() == null) {
+                continue;
+            }
+            List<EmergencyNumber> emergencyNumberList = entry.getValue();
+            Log.d(TAG, "Emergency numbers for subscription id " + entry.getKey());
+
+            // The list of promoted emergency numbers which will be visible on shortcut view.
+            List<EmergencyNumber> promotedList = new ArrayList<>();
+            // A temporary list for non-prioritized emergency numbers.
+            List<EmergencyNumber> tempList = new ArrayList<>();
+
+            for (EmergencyNumber emergencyNumber : emergencyNumberList) {
+                // Emergency numbers in DATABASE are prioritized since they were well-categorized.
+                boolean isFromPrioritizedSource =
+                        emergencyNumber.getEmergencyNumberSources().contains(
+                                EMERGENCY_NUMBER_SOURCE_DATABASE);
+
+                Log.d(TAG, String.format("Number %s, isFromPrioritizedSource %b",
+                        emergencyNumber, isFromPrioritizedSource));
+                if (isFromPrioritizedSource) {
+                    promotedList.add(emergencyNumber);
+                } else {
+                    tempList.add(emergencyNumber);
+                }
+            }
+            // Puts numbers in temp list after prioritized numbers.
+            promotedList.addAll(tempList);
+
+            if (!promotedList.isEmpty()) {
+                promotedEmergencyNumberLists.put(entry.getKey(), promotedList);
+            }
+        }
+
+        if (promotedEmergencyNumberLists.isEmpty()) {
+            Log.w(TAG, "No promoted emergency number found!");
+        }
+        return promotedEmergencyNumberLists.get(SubscriptionManager.getDefaultSubscriptionId());
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cea7903..9f1c96d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1109,6 +1109,8 @@
     <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until charged</string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until charged</string>
+    <!-- [CHAR_LIMIT=40] Label for battery level chart when charge been limited -->
+    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Battery limited temporarily</string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
new file mode 100644
index 0000000..1403631
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.emergencynumber;
+
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public class EmergencyNumberUtilsTest {
+    private static final String TELEPHONY_EMERGENCY_NUMBER = "1234";
+    private static final String USER_OVERRIDE_EMERGENCY_NUMBER = "5678";
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    private EmergencyNumberUtils mUtils;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void getDefaultPoliceNumber_noTelephony_shouldReturnDefault() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+        mUtils = new EmergencyNumberUtils(mContext);
+
+        assertThat(mUtils.getDefaultPoliceNumber()).isEqualTo(
+                EmergencyNumberUtils.FALL_BACK_NUMBER);
+    }
+
+    @Test
+    public void getDefaultPoliceNumber_hasTelephony_shouldLoadFromTelephony() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        addEmergencyNumberToTelephony();
+        mUtils = new EmergencyNumberUtils(mContext);
+
+
+        assertThat(mUtils.getDefaultPoliceNumber()).isEqualTo(TELEPHONY_EMERGENCY_NUMBER);
+    }
+
+    @Test
+    public void getPoliceNumber_hasUserOverride_shouldLoadFromUserOverride() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        addEmergencyNumberToTelephony();
+
+        ContentResolver resolver = RuntimeEnvironment.application.getContentResolver();
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        Settings.Secure.putString(resolver, Settings.Secure.EMERGENCY_GESTURE_CALL_NUMBER,
+                USER_OVERRIDE_EMERGENCY_NUMBER);
+
+        mUtils = new EmergencyNumberUtils(mContext);
+
+        assertThat(mUtils.getPoliceNumber()).isEqualTo(USER_OVERRIDE_EMERGENCY_NUMBER);
+    }
+
+    @Test
+    public void getPoliceNumber_noUserOverride_shouldLoadFromTelephony() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        addEmergencyNumberToTelephony();
+
+        mUtils = new EmergencyNumberUtils(mContext);
+
+        assertThat(mUtils.getPoliceNumber()).isEqualTo(TELEPHONY_EMERGENCY_NUMBER);
+    }
+
+    private void addEmergencyNumberToTelephony() {
+        final int subId = SubscriptionManager.getDefaultSubscriptionId();
+        EmergencyNumber emergencyNumber = mock(EmergencyNumber.class);
+        Map<Integer, List<EmergencyNumber>> numbers = new ArrayMap<>();
+        List<EmergencyNumber> numbersForSubId = new ArrayList<>();
+        numbersForSubId.add(emergencyNumber);
+        numbers.put(subId, numbersForSubId);
+        when(mTelephonyManager.getEmergencyNumberList(
+                EMERGENCY_SERVICE_CATEGORY_POLICE)).thenReturn(numbers);
+        when(emergencyNumber.getNumber()).thenReturn(TELEPHONY_EMERGENCY_NUMBER);
+    }
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a0a5fcb..af12ddd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -315,7 +315,6 @@
                     Settings.Global.KERNEL_CPU_THREAD_READER,
                     Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
                     Settings.Global.LANG_ID_UPDATE_METADATA_URL,
-                    Settings.Global.LATENCY_TRACKER,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3de0fbd..7120cc2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -623,8 +623,7 @@
 
         <service
             android:name=".keyguard.KeyguardService"
-            android:exported="true"
-            android:enabled="@bool/config_enableKeyguardService" />
+            android:exported="true" />
 
         <activity android:name=".keyguard.WorkLockActivity"
                   android:label="@string/accessibility_desc_work_lock"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 63f8b1f..0f94bca 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -21,6 +21,7 @@
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 /**
@@ -30,7 +31,7 @@
  */
 @ProvidesInterface(version = FalsingManager.VERSION)
 public interface FalsingManager {
-    int VERSION = 5;
+    int VERSION = 6;
 
     void onSuccessfulUnlock();
 
@@ -45,6 +46,42 @@
     /** Returns true if the gesture should be rejected. */
     boolean isFalseTouch(int interactionType);
 
+    /**
+     * Returns true if the FalsingManager thinks the last gesure was not a valid tap.
+     *
+     * Accepts one parameter, robustCheck, that distinctly changes behavior. When set to false,
+     * this method simply looks at the last gesture and returns whether it is a tap or not, (as
+     * opposed to a swipe or other non-tap gesture). When set to true, a more thorough analysis
+     * is performed that can include historical interactions and other contextual cues to see
+     * if the tap looks accidental.
+     *
+     * Set robustCheck to true if you want to validate a tap for launching an action, like opening
+     * a notification. Set to false if you simply want to know if the last gesture looked like a
+     * tap.
+     */
+    boolean isFalseTap(boolean robustCheck);
+
+    /**
+     * Returns true if the last two gestures do not look like a double tap.
+     *
+     * Only works on data that has already been reported to the FalsingManager. Be sure that
+     * {@link #onTouchEvent(MotionEvent, int, int)} has already been called for all of the
+     * taps you want considered.
+     *
+     * This looks at the last two gestures on the screen, ensuring that they meet the following
+     * criteria:
+     *
+     *   a) There are at least two gestures.
+     *   b) The last two gestures look like taps.
+     *   c) The last two gestures look like a double tap taken together.
+     *
+     *   This method is _not_ context aware. That is to say, if two taps occur on two neighboring
+     *   views, but are otherwise close to one another, this will report a successful double tap.
+     *   It is up to the caller to decide
+     * @return
+     */
+    boolean isFalseDoubleTap();
+
     void onNotificatonStopDraggingDown();
 
     void setNotificationExpanded();
@@ -103,7 +140,8 @@
 
     void onTouchEvent(MotionEvent ev, int width, int height);
 
-    void dump(PrintWriter pw);
+    /** From com.android.systemui.Dumpable. */
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 
     void cleanup();
 }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 7816d62..b75c2c4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -82,6 +82,28 @@
             android:elegantTextHeight="false"
         />
     </FrameLayout>
+    <FrameLayout
+        android:id="@+id/new_lockscreen_clock_view_large"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/keyguard_status_area"
+        android:paddingTop="20dp"
+        android:visibility="gone">
+        <com.android.keyguard.AnimatableClockView
+            android:id="@+id/animatable_clock_view_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center_horizontal"
+            android:textSize="170dp"
+            android:letterSpacing="0.02"
+            android:lineSpacingMultiplier=".8"
+            android:includeFontPadding="false"
+            android:fontFamily="@font/clock"
+            android:typeface="monospace"
+            android:elegantTextHeight="false"
+        />
+    </FrameLayout>
     <include layout="@layout/keyguard_status_area"
         android:id="@+id/keyguard_status_area"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..d95ab37 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -79,6 +79,9 @@
          is not fully charged, and it's plugged into a slow charger, say that it's charging slowly.  -->
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
+    <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that it's limited temporarily.  -->
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Battery limited temporarily</string>
+
     <!-- When the lock screen is showing and the battery is low, warn user to plug
          in the phone soon. -->
     <string name="keyguard_low_battery">Connect your charger.</string>
diff --git a/packages/SystemUI/res-product/values-de/strings.xml b/packages/SystemUI/res-product/values-de/strings.xml
index 5c8f842..a84413d 100644
--- a/packages/SystemUI/res-product/values-de/strings.xml
+++ b/packages/SystemUI/res-product/values-de/strings.xml
@@ -21,7 +21,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"Smartphone genau platzieren, um es schneller zu laden"</string>
     <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"Smartphone genau platzieren, um es kabellos zu laden"</string>
-    <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Das Android TV-Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke beispielsweise eine Taste oder berühre den Bildschirm."</string>
+    <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Das Android TV-Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke eine Taste."</string>
     <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Das Gerät wird gleich ausgeschaltet. Falls es eingeschaltet bleiben soll, drücke beispielsweise eine Taste oder berühre den Bildschirm."</string>
     <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Keine SIM-Karte im Tablet."</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Keine SIM-Karte im Smartphone."</string>
diff --git a/packages/SystemUI/res/layout/controls_dialog_pin.xml b/packages/SystemUI/res/layout/controls_dialog_pin.xml
index 170b32b..d0ef10b 100644
--- a/packages/SystemUI/res/layout/controls_dialog_pin.xml
+++ b/packages/SystemUI/res/layout/controls_dialog_pin.xml
@@ -27,6 +27,7 @@
       android:layout_height="wrap_content"
       android:minHeight="48dp"
       android:longClickable="false"
+      android:textAlignment="viewStart"
       android:inputType="numberPassword" />
   <CheckBox
       android:id="@+id/controls_pin_use_alpha"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 0ba546e..f984100 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -78,7 +78,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/status_bar_height"
-        android:layout_gravity="top|center_horizontal">
+        android:layout_gravity="top|center_horizontal"
+        android:gravity="center_horizontal">
         <com.android.systemui.statusbar.phone.LockIcon
             android:id="@+id/lock_icon"
             android:layout_width="@dimen/keyguard_lock_width"
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8d86478..6b598da 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -944,7 +944,7 @@
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Application ouverte sans avoir été installée."</string>
     <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Application ouverte sans avoir été installée. Touchez ici pour en savoir plus."</string>
-    <string name="app_info" msgid="5153758994129963243">"Détails de l\'applic."</string>
+    <string name="app_info" msgid="5153758994129963243">"Détails de l\'appli"</string>
     <string name="go_to_web" msgid="636673528981366511">"Ouvrir le navigateur"</string>
     <string name="mobile_data" msgid="4564407557775397216">"Données cellulaires"</string>
     <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index cebe1e8..09ec439 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -44,6 +44,9 @@
         <item>com.android.systemui.wmshell.WMShell</item>
     </string-array>
 
+    <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
+    <integer name="recents_svelte_level">3</integer>
+
     <!-- Show a separate icon for low and high volume on the volume dialog -->
     <bool name="config_showLowMediaVolumeIcon">true</bool>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4407d8f..880dd378 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -161,9 +161,6 @@
     <!-- The number of milliseconds to extend ambient pulse by when prompted (e.g. on touch) -->
     <integer name="ambient_notification_extension_time">10000</integer>
 
-    <!-- Whether to enable KeyguardService or not -->
-    <bool name="config_enableKeyguardService">true</bool>
-
     <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
      card. -->
     <integer name="keyguard_max_notification_count">3</integer>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java
new file mode 100644
index 0000000..4a870f1
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewTreeObserverWrapper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+
+import java.util.HashMap;
+
+public class ViewTreeObserverWrapper {
+
+    private static final HashMap<OnComputeInsetsListener, OnComputeInternalInsetsListener>
+            sOnComputeInsetsListenerMap = new HashMap<>();
+
+    /**
+     * Register a callback to be invoked when the invoked when it is time to
+     * compute the window's insets.
+     *
+     * @param listener The callback to add
+     * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false
+     */
+    public static void addOnComputeInsetsListener(
+            ViewTreeObserver observer, OnComputeInsetsListener listener) {
+        final OnComputeInternalInsetsListener internalListener = internalInOutInfo -> {
+            final InsetsInfo inOutInfo = new InsetsInfo();
+            inOutInfo.contentInsets.set(internalInOutInfo.contentInsets);
+            inOutInfo.visibleInsets.set(internalInOutInfo.visibleInsets);
+            inOutInfo.touchableRegion.set(internalInOutInfo.touchableRegion);
+            listener.onComputeInsets(inOutInfo);
+            internalInOutInfo.contentInsets.set(inOutInfo.contentInsets);
+            internalInOutInfo.visibleInsets.set(inOutInfo.visibleInsets);
+            internalInOutInfo.touchableRegion.set(inOutInfo.touchableRegion);
+            internalInOutInfo.setTouchableInsets(inOutInfo.mTouchableInsets);
+        };
+        sOnComputeInsetsListenerMap.put(listener, internalListener);
+        observer.addOnComputeInternalInsetsListener(internalListener);
+    }
+
+    /**
+     * Remove a previously installed insets computation callback
+     *
+     * @param victim The callback to remove
+     * @throws IllegalStateException If {@link ViewTreeObserver#isAlive()} returns false
+     * @see #addOnComputeInsetsListener(ViewTreeObserver, OnComputeInsetsListener)
+     */
+    public void removeOnComputeInsetsListener(
+            ViewTreeObserver observer, OnComputeInsetsListener victim) {
+        final OnComputeInternalInsetsListener listener = sOnComputeInsetsListenerMap.get(victim);
+        if (listener != null) {
+            observer.removeOnComputeInternalInsetsListener(listener);
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when layout has
+     * completed and the client can compute its interior insets.
+     */
+    public interface OnComputeInsetsListener {
+        /**
+         * Callback method to be invoked when layout has completed and the
+         * client can compute its interior insets.
+         *
+         * @param inoutInfo Should be filled in by the implementation with
+         * the information about the insets of the window.  This is called
+         * with whatever values the previous OnComputeInsetsListener
+         * returned, if there are multiple such listeners in the window.
+         */
+        void onComputeInsets(InsetsInfo inoutInfo);
+    }
+
+    /**
+     * Parameters used with OnComputeInsetsListener.
+     */
+    public final static class InsetsInfo {
+
+        /**
+         * Offsets from the frame of the window at which the content of
+         * windows behind it should be placed.
+         */
+        public final Rect contentInsets = new Rect();
+
+        /**
+         * Offsets from the frame of the window at which windows behind it
+         * are visible.
+         */
+        public final Rect visibleInsets = new Rect();
+
+        /**
+         * Touchable region defined relative to the origin of the frame of the window.
+         * Only used when {@link #setTouchableInsets(int)} is called with
+         * the option {@link #TOUCHABLE_INSETS_REGION}.
+         */
+        public final Region touchableRegion = new Region();
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the entire window frame
+         * can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_FRAME =
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the content insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_CONTENT =
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the visible insets can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_VISIBLE =
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+
+        /**
+         * Option for {@link #setTouchableInsets(int)}: the area inside of
+         * the provided touchable region in {@link #touchableRegion} can be touched.
+         */
+        public static final int TOUCHABLE_INSETS_REGION =
+                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+
+        /**
+         * Set which parts of the window can be touched: either
+         * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+         * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
+         */
+        public void setTouchableInsets(int val) {
+            mTouchableInsets = val;
+        }
+
+        int mTouchableInsets;
+
+        @Override
+        public int hashCode() {
+            int result = contentInsets.hashCode();
+            result = 31 * result + visibleInsets.hashCode();
+            result = 31 * result + touchableRegion.hashCode();
+            result = 31 * result + mTouchableInsets;
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            final InsetsInfo other = (InsetsInfo) o;
+            return mTouchableInsets == other.mTouchableInsets &&
+                    contentInsets.equals(other.contentInsets) &&
+                    visibleInsets.equals(other.visibleInsets) &&
+                    touchableRegion.equals(other.touchableRegion);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d731840..68405a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -76,6 +76,7 @@
      * Frame for clock when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
      */
     private FrameLayout mNewLockscreenClockFrame;
+    private FrameLayout mNewLockscreenLargeClockFrame;
 
     /**
      * Frame for default and custom clock.
@@ -154,14 +155,12 @@
             mNewLockscreenClockFrame.setVisibility(VISIBLE);
 
             statusAreaLP.removeRule(RelativeLayout.BELOW);
-            statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.new_lockscreen_clock_view);
             statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
         } else {
             setPaddingRelative(0, 0, 0, 0);
             mSmallClockFrame.setVisibility(VISIBLE);
             mNewLockscreenClockFrame.setVisibility(GONE);
 
-            statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
             statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
             statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
         }
@@ -175,6 +174,7 @@
         mClockView = findViewById(R.id.default_clock_view);
         mClockViewBold = findViewById(R.id.default_clock_view_bold);
         mNewLockscreenClockFrame = findViewById(R.id.new_lockscreen_clock_view);
+        mNewLockscreenLargeClockFrame = findViewById(R.id.new_lockscreen_clock_view_large);
         mSmallClockFrame = findViewById(R.id.clock_view);
         mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
     }
@@ -292,6 +292,33 @@
         mClockViewBold.setFormat24Hour(format);
     }
 
+    private void updateClockLayout(boolean useLargeClock) {
+        if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) return;
+
+        Fade fadeIn = new Fade();
+        fadeIn.setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION);
+        fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+
+        Fade fadeOut = new Fade();
+        fadeOut.setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2);
+        fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+
+        if (useLargeClock) {
+            TransitionManager.beginDelayedTransition(mNewLockscreenClockFrame, fadeOut);
+            TransitionManager.beginDelayedTransition(mNewLockscreenLargeClockFrame, fadeIn);
+
+            mNewLockscreenClockFrame.setVisibility(View.INVISIBLE);
+            addView(mNewLockscreenLargeClockFrame);
+            mNewLockscreenLargeClockFrame.setVisibility(View.VISIBLE);
+        } else {
+            TransitionManager.beginDelayedTransition(mNewLockscreenClockFrame, fadeIn);
+            TransitionManager.beginDelayedTransition(mNewLockscreenLargeClockFrame, fadeOut);
+
+            removeView(mNewLockscreenLargeClockFrame);
+            mNewLockscreenClockFrame.setVisibility(View.VISIBLE);
+        }
+    }
+
     /**
      * Set the amount (ratio) that the device has transitioned to doze.
      *
@@ -312,6 +339,8 @@
         if (hasVisibleNotifications == mHasVisibleNotifications) {
             return;
         }
+        updateClockLayout(!hasVisibleNotifications);
+
         mHasVisibleNotifications = hasVisibleNotifications;
         if (mDarkAmount == 0f && mBigClockContainer != null) {
             // Starting a fade transition since the visibility of the big clock will change.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6c69534..c5c36e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -61,6 +61,7 @@
      */
     private AnimatableClockController mNewLockScreenClockViewController;
     private FrameLayout mNewLockScreenClockFrame;
+    private AnimatableClockController mNewLockScreenLargeClockViewController;
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
@@ -189,6 +190,7 @@
     void refresh() {
         if (mNewLockScreenClockViewController != null) {
             mNewLockScreenClockViewController.refreshTime();
+            mNewLockScreenLargeClockViewController.refreshTime();
         }
 
         mView.refresh();
@@ -221,9 +223,15 @@
                                 mView.findViewById(R.id.animatable_clock_view),
                                 mStatusBarStateController);
                 mNewLockScreenClockViewController.init();
+                mNewLockScreenLargeClockViewController =
+                        new AnimatableClockController(
+                                mView.findViewById(R.id.animatable_clock_view_large),
+                                mStatusBarStateController);
+                mNewLockScreenLargeClockViewController.init();
             }
         } else {
             mNewLockScreenClockViewController = null;
+            mNewLockScreenLargeClockViewController = null;
         }
         mView.updateLockScreenMode(mLockScreenMode);
         updateAodIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8aa3493c..6151ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -24,6 +24,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.graphics.RectF;
 import android.os.Handler;
@@ -78,9 +79,9 @@
 
     private float mInitialTouchPos;
     private float mPerpendicularInitialTouchPos;
-    private boolean mDragging;
+    private boolean mIsSwiping;
     private boolean mSnappingChild;
-    private View mCurrView;
+    private View mTouchedView;
     private boolean mCanCurrViewBeDimissed;
     private float mDensityScale;
     private float mTranslation = 0;
@@ -95,14 +96,14 @@
 
         @Override
         public void run() {
-            if (mCurrView != null && !mLongPressSent) {
+            if (mTouchedView != null && !mLongPressSent) {
                 mLongPressSent = true;
-                if (mCurrView instanceof ExpandableNotificationRow) {
-                    mCurrView.getLocationOnScreen(mViewOffset);
+                if (mTouchedView instanceof ExpandableNotificationRow) {
+                    mTouchedView.getLocationOnScreen(mViewOffset);
                     final int x = (int) mDownLocation[0] - mViewOffset[0];
                     final int y = (int) mDownLocation[1] - mViewOffset[1];
-                    mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-                    ((ExpandableNotificationRow) mCurrView).doLongClickCallback(x, y);
+                    mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+                    ((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y);
                 }
             }
         }
@@ -281,10 +282,10 @@
 
     @Override
     public boolean onInterceptTouchEvent(final MotionEvent ev) {
-        if (mCurrView instanceof ExpandableNotificationRow) {
-            NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
+        if (mTouchedView instanceof ExpandableNotificationRow) {
+            NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mTouchedView).getProvider();
             if (nmr != null) {
-                mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
+                mMenuRowIntercepting = nmr.onInterceptTouchEvent(mTouchedView, ev);
             }
         }
         final int action = ev.getAction();
@@ -292,19 +293,19 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mTouchAboveFalsingThreshold = false;
-                mDragging = false;
+                mIsSwiping = false;
                 mSnappingChild = false;
                 mLongPressSent = false;
                 mVelocityTracker.clear();
-                mCurrView = mCallback.getChildAtPosition(ev);
+                mTouchedView = mCallback.getChildAtPosition(ev);
 
-                if (mCurrView != null) {
-                    onDownUpdate(mCurrView, ev);
-                    mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
+                if (mTouchedView != null) {
+                    onDownUpdate(mTouchedView, ev);
+                    mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mTouchedView);
                     mVelocityTracker.addMovement(ev);
                     mInitialTouchPos = getPos(ev);
                     mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
-                    mTranslation = getTranslation(mCurrView);
+                    mTranslation = getTranslation(mTouchedView);
                     mDownLocation[0] = ev.getRawX();
                     mDownLocation[1] = ev.getRawY();
                     mHandler.postDelayed(mPerformLongPress, mLongPressTimeout);
@@ -312,7 +313,7 @@
                 break;
 
             case MotionEvent.ACTION_MOVE:
-                if (mCurrView != null && !mLongPressSent) {
+                if (mTouchedView != null && !mLongPressSent) {
                     mVelocityTracker.addMovement(ev);
                     float pos = getPos(ev);
                     float perpendicularPos = getPerpendicularPos(ev);
@@ -325,11 +326,11 @@
                             : mPagingTouchSlop;
                     if (Math.abs(delta) > pagingTouchSlop
                             && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
-                        if (mCallback.canChildBeDragged(mCurrView)) {
-                            mCallback.onBeginDrag(mCurrView);
-                            mDragging = true;
+                        if (mCallback.canChildBeDragged(mTouchedView)) {
+                            mCallback.onBeginDrag(mTouchedView);
+                            mIsSwiping = true;
                             mInitialTouchPos = getPos(ev);
-                            mTranslation = getTranslation(mCurrView);
+                            mTranslation = getTranslation(mTouchedView);
                         }
                         cancelLongPress();
                     } else if (ev.getClassification() == MotionEvent.CLASSIFICATION_DEEP_PRESS
@@ -343,16 +344,16 @@
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
-                mDragging = false;
-                mCurrView = null;
+                final boolean captured = (mIsSwiping || mLongPressSent || mMenuRowIntercepting);
+                mIsSwiping = false;
+                mTouchedView = null;
                 mLongPressSent = false;
                 mMenuRowIntercepting = false;
                 cancelLongPress();
                 if (captured) return true;
                 break;
         }
-        return mDragging || mLongPressSent || mMenuRowIntercepting;
+        return mIsSwiping || mLongPressSent || mMenuRowIntercepting;
     }
 
     /**
@@ -451,6 +452,7 @@
                 }
                 if (!mCancelled || wasRemoved) {
                     mCallback.onChildDismissed(animView);
+                    resetSwipeState();
                 }
                 if (endAction != null) {
                     endAction.run();
@@ -501,6 +503,7 @@
                     updateSwipeProgressFromOffset(animView, canBeDismissed);
                     onChildSnappedBack(animView, targetLeft);
                     mCallback.onChildSnappedBack(animView, targetLeft);
+                    resetSwipeState();
                 }
             }
         });
@@ -563,7 +566,7 @@
      * @param targetLeft the target to snap to.
      */
     public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
-        if ((mDragging && mCurrView == view) || mSnappingChild) {
+        if ((mIsSwiping && mTouchedView == view) || mSnappingChild) {
             return;
         }
         boolean needToSnap = false;
@@ -589,7 +592,7 @@
             return true;
         }
 
-        if (!mDragging && !mMenuRowIntercepting) {
+        if (!mIsSwiping && !mMenuRowIntercepting) {
             if (mCallback.getChildAtPosition(ev) != null) {
 
                 // We are dragging directly over a card, make sure that we also catch the gesture
@@ -610,7 +613,7 @@
         switch (action) {
             case MotionEvent.ACTION_OUTSIDE:
             case MotionEvent.ACTION_MOVE:
-                if (mCurrView != null) {
+                if (mTouchedView != null) {
                     float delta = getPos(ev) - mInitialTouchPos;
                     float absDelta = Math.abs(delta);
                     if (absDelta >= getFalsingThreshold()) {
@@ -618,9 +621,10 @@
                     }
                     // don't let items that can't be dismissed be dragged more than
                     // maxScrollDistance
-                    if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(mCurrView,
+                    if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
+                            mTouchedView,
                             delta > 0)) {
-                        float size = getSize(mCurrView);
+                        float size = getSize(mTouchedView);
                         float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
                         if (absDelta >= size) {
                             delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
@@ -636,32 +640,30 @@
                         }
                     }
 
-                    setTranslation(mCurrView, mTranslation + delta);
-                    updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
-                    onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
+                    setTranslation(mTouchedView, mTranslation + delta);
+                    updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
+                    onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mCurrView == null) {
+                if (mTouchedView == null) {
                     break;
                 }
                 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
                 float velocity = getVelocity(mVelocityTracker);
 
-                if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
+                if (!handleUpEvent(ev, mTouchedView, velocity, getTranslation(mTouchedView))) {
                     if (isDismissGesture(ev)) {
-                        // flingadingy
-                        dismissChild(mCurrView, velocity,
+                        dismissChild(mTouchedView, velocity,
                                 !swipedFastEnough() /* useAccelerateInterpolator */);
                     } else {
-                        // snappity
-                        mCallback.onDragCancelled(mCurrView);
-                        snapChild(mCurrView, 0 /* leftTarget */, velocity);
+                        mCallback.onDragCancelled(mTouchedView);
+                        snapChild(mTouchedView, 0 /* leftTarget */, velocity);
                     }
-                    mCurrView = null;
+                    mTouchedView = null;
                 }
-                mDragging = false;
+                mIsSwiping = false;
                 break;
         }
         return true;
@@ -689,17 +691,18 @@
     }
 
     protected boolean swipedFarEnough() {
-        float translation = getTranslation(mCurrView);
+        float translation = getTranslation(mTouchedView);
         return DISMISS_IF_SWIPED_FAR_ENOUGH
-                && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mCurrView);
+                && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
+                mTouchedView);
     }
 
     public boolean isDismissGesture(MotionEvent ev) {
-        float translation = getTranslation(mCurrView);
+        float translation = getTranslation(mTouchedView);
         return ev.getActionMasked() == MotionEvent.ACTION_UP
                 && !mFalsingManager.isUnlockingDisabled()
                 && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
-                && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
+                && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0);
     }
 
     /** Returns true if the gesture should be rejected. */
@@ -715,7 +718,7 @@
 
     protected boolean swipedFastEnough() {
         float velocity = getVelocity(mVelocityTracker);
-        float translation = getTranslation(mCurrView);
+        float translation = getTranslation(mTouchedView);
         boolean ret = (Math.abs(velocity) > getEscapeVelocity())
                 && (velocity > 0) == (translation > 0);
         return ret;
@@ -726,6 +729,20 @@
         return false;
     }
 
+    public boolean isSwiping() {
+        return mIsSwiping;
+    }
+
+    @Nullable
+    public View getSwipedView() {
+        return mIsSwiping ? mTouchedView : null;
+    }
+
+    public void resetSwipeState() {
+        mTouchedView = null;
+        mIsSwiping = false;
+    }
+
     public interface Callback {
         View getChildAtPosition(MotionEvent ev);
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index bb7906e..714d267bb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -23,6 +23,7 @@
 import android.content.ContentProvider;
 import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -94,7 +95,7 @@
                         } catch (NoSuchMethodException
                                 | IllegalAccessException
                                 | InvocationTargetException e) {
-                            // no-op
+                            Log.w(TAG, "No injector for class: " + contentProvider.getClass(), e);
                         }
                     }
             );
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 2b33f8c..182b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -101,7 +101,7 @@
 
     @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
 
-    private @ContainerState int mContainerState = STATE_UNKNOWN;
+    @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
@@ -632,10 +632,11 @@
         mWindowManager.removeView(this);
     }
 
-    private void onDialogAnimatedIn() {
+    @VisibleForTesting
+    void onDialogAnimatedIn() {
         if (mContainerState == STATE_PENDING_DISMISS) {
             Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
-            animateAway(false /* sendReason */, 0);
+            animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
             return;
         }
         mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3fc9f4d..7bb8a84 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -187,12 +187,14 @@
                 // TODO(b/152419866): Use the UDFPS window type when it becomes available.
                 WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSLUCENT);
         mCoreLayoutParams.setTitle(TAG);
         mCoreLayoutParams.setFitInsetsTypes(0);
+        mCoreLayoutParams.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
         mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
         mView.setSensorProperties(mSensorProps);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
deleted file mode 100644
index 4585fe9..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AccelerationClassifier.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.view.MotionEvent;
-
-import java.util.HashMap;
-
-/**
- * A classifier which looks at the speed and distance between successive points of a Stroke.
- * It looks at two consecutive speeds between two points and calculates the ratio between them.
- * The final result is the maximum of these values. It does the same for distances. If some speed
- * or distance is equal to zero then the ratio between this and the next part is not calculated. To
- * the duration of each part there is added one nanosecond so that it is always possible to
- * calculate the speed of a part.
- */
-public class AccelerationClassifier extends StrokeClassifier {
-    private final HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
-    public AccelerationClassifier(ClassifierData classifierData) {
-        mClassifierData = classifierData;
-    }
-
-    @Override
-    public String getTag() {
-        return "ACC";
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mStrokeMap.clear();
-        }
-
-        for (int i = 0; i < event.getPointerCount(); i++) {
-            Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
-            Point point = stroke.getPoints().get(stroke.getPoints().size() - 1);
-            if (mStrokeMap.get(stroke) == null) {
-                mStrokeMap.put(stroke, new Data(point));
-            } else {
-                mStrokeMap.get(stroke).addPoint(point);
-            }
-        }
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        Data data = mStrokeMap.get(stroke);
-        return 2 * SpeedRatioEvaluator.evaluate(data.maxSpeedRatio);
-    }
-
-    private static class Data {
-
-        static final float MILLIS_TO_NANOS = 1e6f;
-
-        Point previousPoint;
-        float previousSpeed = 0;
-        float maxSpeedRatio = 0;
-
-        public Data(Point point) {
-            previousPoint = point;
-        }
-
-        public void addPoint(Point point) {
-            float distance = previousPoint.dist(point);
-            float duration = (float) (point.timeOffsetNano - previousPoint.timeOffsetNano + 1);
-            float speed = distance / duration;
-
-            if (duration > 20 * MILLIS_TO_NANOS || duration < 5 * MILLIS_TO_NANOS) {
-                // reject this segment and ensure we won't use data about it in the next round.
-                previousSpeed = 0;
-                previousPoint = point;
-                return;
-            }
-            if (previousSpeed != 0.0f) {
-                maxSpeedRatio = Math.max(maxSpeedRatio, speed / previousSpeed);
-            }
-
-            previousSpeed = speed;
-            previousPoint = point;
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
deleted file mode 100644
index 6d13973..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.os.Build;
-import android.os.SystemProperties;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A classifier which calculates the variance of differences between successive angles in a stroke.
- * For each stroke it keeps its last three points. If some successive points are the same, it
- * ignores the repetitions. If a new point is added, the classifier calculates the angle between
- * the last three points. After that, it calculates the difference between this angle and the
- * previously calculated angle. Then it calculates the variance of the differences from a stroke.
- * To the differences there is artificially added value 0.0 and the difference between the first
- * angle and PI (angles are in radians). It helps with strokes which have few points and punishes
- * more strokes which are not smooth.
- *
- * This classifier also tries to split the stroke into two parts in the place in which the biggest
- * angle is. It calculates the angle variance of the two parts and sums them up. The reason the
- * classifier is doing this, is because some human swipes at the beginning go for a moment in one
- * direction and then they rapidly change direction for the rest of the stroke (like a tick). The
- * final result is the minimum of angle variance of the whole stroke and the sum of angle variances
- * of the two parts split up. The classifier tries the tick option only if the first part is
- * shorter than the second part.
- *
- * Additionally, the classifier classifies the angles as left angles (those angles which value is
- * in [0.0, PI - ANGLE_DEVIATION) interval), straight angles
- * ([PI - ANGLE_DEVIATION, PI + ANGLE_DEVIATION] interval) and right angles
- * ((PI + ANGLE_DEVIATION, 2 * PI) interval) and then calculates the percentage of angles which are
- * in the same direction (straight angles can be left angels or right angles)
- */
-public class AnglesClassifier extends StrokeClassifier {
-    private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
-    public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.ang",
-            Build.IS_DEBUGGABLE);
-
-    private static String TAG = "ANG";
-
-    public AnglesClassifier(ClassifierData classifierData) {
-        mClassifierData = classifierData;
-    }
-
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mStrokeMap.clear();
-        }
-
-        for (int i = 0; i < event.getPointerCount(); i++) {
-            Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
-
-            if (mStrokeMap.get(stroke) == null) {
-                mStrokeMap.put(stroke, new Data());
-            }
-            mStrokeMap.get(stroke).addPoint(stroke.getPoints().get(stroke.getPoints().size() - 1));
-        }
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        Data data = mStrokeMap.get(stroke);
-        return AnglesVarianceEvaluator.evaluate(data.getAnglesVariance(), type)
-                + AnglesPercentageEvaluator.evaluate(data.getAnglesPercentage(), type);
-    }
-
-    private static class Data {
-        private final float ANGLE_DEVIATION = (float) Math.PI / 20.0f;
-
-        private List<Point> mLastThreePoints = new ArrayList<>();
-        private float mFirstAngleVariance;
-        private float mPreviousAngle;
-        private float mBiggestAngle;
-        private float mSumSquares;
-        private float mSecondSumSquares;
-        private float mSum;
-        private float mSecondSum;
-        private float mCount;
-        private float mSecondCount;
-        private float mFirstLength;
-        private float mLength;
-        private float mAnglesCount;
-        private float mLeftAngles;
-        private float mRightAngles;
-        private float mStraightAngles;
-
-        public Data() {
-            mFirstAngleVariance = 0.0f;
-            mPreviousAngle = (float) Math.PI;
-            mBiggestAngle = 0.0f;
-            mSumSquares = mSecondSumSquares = 0.0f;
-            mSum = mSecondSum = 0.0f;
-            mCount = mSecondCount = 1.0f;
-            mLength = mFirstLength = 0.0f;
-            mAnglesCount = mLeftAngles = mRightAngles = mStraightAngles = 0.0f;
-        }
-
-        public void addPoint(Point point) {
-            // Checking if the added point is different than the previously added point
-            // Repetitions are being ignored so that proper angles are calculated.
-            if (mLastThreePoints.isEmpty()
-                    || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(point)) {
-                if (!mLastThreePoints.isEmpty()) {
-                    mLength += mLastThreePoints.get(mLastThreePoints.size() - 1).dist(point);
-                }
-                mLastThreePoints.add(point);
-                if (mLastThreePoints.size() == 4) {
-                    mLastThreePoints.remove(0);
-
-                    float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0),
-                            mLastThreePoints.get(2));
-
-                    mAnglesCount++;
-                    if (angle < Math.PI - ANGLE_DEVIATION) {
-                        mLeftAngles++;
-                    } else if (angle <= Math.PI + ANGLE_DEVIATION) {
-                        mStraightAngles++;
-                    } else {
-                        mRightAngles++;
-                    }
-
-                    float difference = angle - mPreviousAngle;
-
-                    // If this is the biggest angle of the stroke so then we save the value of
-                    // the angle variance so far and start to count the values for the angle
-                    // variance of the second part.
-                    if (mBiggestAngle < angle) {
-                        mBiggestAngle = angle;
-                        mFirstLength = mLength;
-                        mFirstAngleVariance = getAnglesVariance(mSumSquares, mSum, mCount);
-                        mSecondSumSquares = 0.0f;
-                        mSecondSum = 0.0f;
-                        mSecondCount = 1.0f;
-                    } else {
-                        mSecondSum += difference;
-                        mSecondSumSquares += difference * difference;
-                        mSecondCount += 1.0;
-                    }
-
-                    mSum += difference;
-                    mSumSquares += difference * difference;
-                    mCount += 1.0;
-                    mPreviousAngle = angle;
-                }
-            }
-        }
-
-        public float getAnglesVariance(float sumSquares, float sum, float count) {
-            return sumSquares / count - (sum / count) * (sum / count);
-        }
-
-        public float getAnglesVariance() {
-            float anglesVariance = getAnglesVariance(mSumSquares, mSum, mCount);
-            if (VERBOSE) {
-                FalsingLog.i(TAG, "getAnglesVariance: (first pass) " + anglesVariance);
-                FalsingLog.i(TAG, "   - mFirstLength=" + mFirstLength);
-                FalsingLog.i(TAG, "   - mLength=" + mLength);
-            }
-            if (mFirstLength < mLength / 2f) {
-                anglesVariance = Math.min(anglesVariance, mFirstAngleVariance
-                        + getAnglesVariance(mSecondSumSquares, mSecondSum, mSecondCount));
-                if (VERBOSE) FalsingLog.i(TAG, "getAnglesVariance: (second pass) " + anglesVariance);
-            }
-            return anglesVariance;
-        }
-
-        public float getAnglesPercentage() {
-            if (mAnglesCount == 0.0f) {
-                if (VERBOSE) FalsingLog.i(TAG, "getAnglesPercentage: count==0, result=1");
-                return 1.0f;
-            }
-            final float result = (Math.max(mLeftAngles, mRightAngles) + mStraightAngles) / mAnglesCount;
-            if (VERBOSE) {
-                FalsingLog.i(TAG, "getAnglesPercentage: left=" + mLeftAngles + " right="
-                        + mRightAngles + " straight=" + mStraightAngles + " count=" + mAnglesCount
-                        + " result=" + result);
-            }
-            return result;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
deleted file mode 100644
index e6e42f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesPercentageEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class AnglesPercentageEvaluator {
-    public static float evaluate(float value, int type) {
-        final boolean secureUnlock = type == Classifier.BOUNCER_UNLOCK;
-        float evaluation = 0.0f;
-        if (value < 1.00 && !secureUnlock) evaluation++;
-        if (value < 0.90 && !secureUnlock) evaluation++;
-        if (value < 0.70) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java
deleted file mode 100644
index 9ffe783..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesVarianceEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class AnglesVarianceEvaluator {
-    public static float evaluate(float value, int type) {
-        float evaluation = 0.0f;
-        if (value > 0.20) evaluation++;
-        if (value > 0.40) evaluation++;
-        if (value > 0.80) evaluation++;
-        if (value > 1.50) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index ae7d142..c7ad3e8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -54,11 +54,6 @@
     public @interface InteractionType {}
 
     /**
-     * Contains all the information about touch events from which the classifier can query
-     */
-    protected ClassifierData mClassifierData;
-
-    /**
      * Informs the classifier that a new touch event has occurred
      */
     public void onTouchEvent(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java b/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
deleted file mode 100644
index 587abba..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ClassifierData.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.util.SparseArray;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * Contains data which is used to classify interaction sequences on the lockscreen. It does, for
- * example, provide information on the current touch state.
- */
-public class ClassifierData {
-    private static final long MINIMUM_DT_NANOS = 16666666;  // 60Hz
-    private static final long MINIMUM_DT_SMEAR_NANOS = 2500000;  // 2.5ms
-
-    private SparseArray<Stroke> mCurrentStrokes = new SparseArray<>();
-    private ArrayList<Stroke> mEndingStrokes = new ArrayList<>();
-    private final float mDpi;
-
-    public ClassifierData(float dpi) {
-        mDpi = dpi;
-    }
-
-    /** Returns true if the event should be considered, false otherwise. */
-    public boolean update(MotionEvent event) {
-        // We limit to 60hz sampling. Drop anything happening faster than that.
-        // Legacy code was created with an assumed sampling rate. As devices increase their
-        // sampling rate, this creates potentialy false positives.
-        if (event.getActionMasked() == MotionEvent.ACTION_MOVE
-                && mCurrentStrokes.size() != 0
-                && event.getEventTimeNano() - mCurrentStrokes.valueAt(0).getLastEventTimeNano()
-                        < MINIMUM_DT_NANOS - MINIMUM_DT_SMEAR_NANOS) {
-            return false;
-        }
-
-        mEndingStrokes.clear();
-        int action = event.getActionMasked();
-        if (action == MotionEvent.ACTION_DOWN) {
-            mCurrentStrokes.clear();
-        }
-
-        for (int i = 0; i < event.getPointerCount(); i++) {
-            int id = event.getPointerId(i);
-            if (mCurrentStrokes.get(id) == null) {
-                mCurrentStrokes.put(id, new Stroke(event.getEventTimeNano(), mDpi));
-            }
-            mCurrentStrokes.get(id).addPoint(event.getX(i), event.getY(i),
-                    event.getEventTimeNano());
-
-            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
-                    || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
-                mEndingStrokes.add(getStroke(id));
-            }
-        }
-
-        return true;
-    }
-
-    public void cleanUp(MotionEvent event) {
-        mEndingStrokes.clear();
-        int action = event.getActionMasked();
-        for (int i = 0; i < event.getPointerCount(); i++) {
-            int id = event.getPointerId(i);
-            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
-                    || (action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
-                mCurrentStrokes.remove(id);
-            }
-        }
-    }
-
-    /**
-     * @return the list of Strokes which are ending in the recently added MotionEvent
-     */
-    public ArrayList<Stroke> getEndingStrokes() {
-        return mEndingStrokes;
-    }
-
-    /**
-     * @param id the id from MotionEvent
-     * @return the Stroke assigned to the id
-     */
-    public Stroke getStroke(int id) {
-        return mCurrentStrokes.get(id);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
deleted file mode 100644
index 610e219..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DirectionClassifier.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the general direction of a stroke and evaluates it depending on
- * the type of action that takes place.
- */
-public class DirectionClassifier extends StrokeClassifier {
-    public DirectionClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "DIR";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        Point firstPoint = stroke.getPoints().get(0);
-        Point lastPoint = stroke.getPoints().get(stroke.getPoints().size() - 1);
-        return DirectionEvaluator.evaluate(lastPoint.x - firstPoint.x, lastPoint.y - firstPoint.y,
-                type);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java
deleted file mode 100644
index 78b4168..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DirectionEvaluator.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class DirectionEvaluator {
-    public static float evaluate(float xDiff, float yDiff, int type) {
-        float falsingEvaluation = 5.5f;
-        boolean vertical = Math.abs(yDiff) >= Math.abs(xDiff);
-        switch (type) {
-            case Classifier.QUICK_SETTINGS:
-            case Classifier.PULSE_EXPAND:
-            case Classifier.NOTIFICATION_DRAG_DOWN:
-                if (!vertical || yDiff <= 0.0) {
-                    return falsingEvaluation;
-                }
-                break;
-            case Classifier.NOTIFICATION_DISMISS:
-                if (vertical) {
-                    return falsingEvaluation;
-                }
-                break;
-            case Classifier.UNLOCK:
-            case Classifier.BOUNCER_UNLOCK:
-                if (!vertical || yDiff >= 0.0) {
-                    return falsingEvaluation;
-                }
-                break;
-            case Classifier.LEFT_AFFORDANCE:
-                if (xDiff < 0.0 && yDiff > 0.0) {
-                    return falsingEvaluation;
-                }
-                break;
-            case Classifier.RIGHT_AFFORDANCE:
-                if (xDiff > 0.0 && yDiff > 0.0) {
-                    return falsingEvaluation;
-                }
-            default:
-                break;
-        }
-        return 0.0f;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
deleted file mode 100644
index 77fda20..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountClassifier.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the ratio between the duration of the stroke and its number of
- * points.
- */
-public class DurationCountClassifier extends StrokeClassifier {
-    public DurationCountClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "DUR";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        return DurationCountEvaluator.evaluate(stroke.getDurationSeconds() / stroke.getCount());
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java
deleted file mode 100644
index 5395983..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/DurationCountEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-
-public class DurationCountEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 0.0105) evaluation++;
-        if (value < 0.00909) evaluation++;
-        if (value < 0.00667) evaluation++;
-        if (value > 0.0333) evaluation++;
-        if (value > 0.0500) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
deleted file mode 100644
index de8a188..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthClassifier.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the distance between the first and the last point from the stroke.
- */
-public class EndPointLengthClassifier extends StrokeClassifier {
-    public EndPointLengthClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "END_LNGTH";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        return EndPointLengthEvaluator.evaluate(stroke.getEndPointLength());
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java
deleted file mode 100644
index bb2f1c4..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointLengthEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class EndPointLengthEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 0.05) evaluation += 2.0;
-        if (value < 0.1) evaluation += 2.0;
-        if (value < 0.2) evaluation += 2.0;
-        if (value < 0.3) evaluation += 2.0;
-        if (value < 0.4) evaluation += 2.0;
-        if (value < 0.5) evaluation += 2.0;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
deleted file mode 100644
index 9b6ddc8..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioClassifier.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the ratio between the total length covered by the stroke and the
- * distance between the first and last point from this stroke.
- */
-public class EndPointRatioClassifier extends StrokeClassifier {
-    public EndPointRatioClassifier(ClassifierData classifierData) {
-        mClassifierData = classifierData;
-    }
-
-    @Override
-    public String getTag() {
-        return "END_RTIO";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        float ratio;
-        if (stroke.getTotalLength() == 0.0f) {
-            ratio = 1.0f;
-        } else {
-            ratio = stroke.getEndPointLength() / stroke.getTotalLength();
-        }
-        return EndPointRatioEvaluator.evaluate(ratio);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java
deleted file mode 100644
index 529fcec..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/EndPointRatioEvaluator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class EndPointRatioEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 0.85) evaluation++;
-        if (value < 0.75) evaluation++;
-        if (value < 0.65) evaluation++;
-        if (value < 0.55) evaluation++;
-        if (value < 0.45) evaluation++;
-        if (value < 0.35) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
deleted file mode 100644
index 8105c6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2016 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.classifier;
-
-import android.app.ActivityThread;
-import android.app.Application;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Keeps track of interesting falsing data.
- *
- * By default the log only gets collected on userdebug builds. To turn it on on user:
- *  adb shell setprop debug.falsing_log true
- *
- * The log gets dumped as part of the SystemUI services. To dump on demand:
- *  adb shell dumpsys activity service com.android.systemui StatusBar | grep -A 999 FALSING | less
- *
- * To dump into logcat:
- *  adb shell setprop debug.falsing_logcat true
- *
- * To adjust the log buffer size:
- *  adb shell setprop debug.falsing_log_size 200
- */
-public class FalsingLog {
-    public static final boolean ENABLED = SystemProperties.getBoolean("debug.falsing_log",
-            Build.IS_DEBUGGABLE);
-    private static final boolean LOGCAT = SystemProperties.getBoolean("debug.falsing_logcat",
-            false);
-
-    public static final boolean VERBOSE = false;
-
-    private static final int MAX_SIZE = SystemProperties.getInt("debug.falsing_log_size", 100);
-
-    private static final String TAG = "FalsingLog";
-
-    private final ArrayDeque<String> mLog = new ArrayDeque<>(MAX_SIZE);
-    private final SimpleDateFormat mFormat = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
-
-    private static FalsingLog sInstance;
-
-    private FalsingLog() {
-    }
-
-    public static void v(String tag, String s) {
-        if (!VERBOSE) {
-            return;
-        }
-        if (LOGCAT) {
-            Log.v(TAG, tag + "\t" + s);
-        }
-        log("V", tag, s);
-    }
-
-    public static void i(String tag, String s) {
-        if (LOGCAT) {
-            Log.i(TAG, tag + "\t" + s);
-        }
-        log("I", tag, s);
-    }
-
-    public static void wLogcat(String tag, String s) {
-        Log.w(TAG, tag + "\t" + s);
-        log("W", tag, s);
-    }
-
-    public static void w(String tag, String s) {
-        if (LOGCAT) {
-            Log.w(TAG, tag + "\t" + s);
-        }
-        log("W", tag, s);
-    }
-
-    public static void e(String tag, String s) {
-        if (LOGCAT) {
-            Log.e(TAG, tag + "\t" + s);
-        }
-        log("E", tag, s);
-    }
-
-    public static synchronized void log(String level, String tag, String s) {
-        if (!ENABLED) {
-            return;
-        }
-        if (sInstance == null) {
-            sInstance = new FalsingLog();
-        }
-
-        if (sInstance.mLog.size() >= MAX_SIZE) {
-            sInstance.mLog.removeFirst();
-        }
-        String entry = new StringBuilder().append(sInstance.mFormat.format(new Date()))
-            .append(" ").append(level).append(" ")
-            .append(tag).append(" ").append(s).toString();
-        sInstance.mLog.add(entry);
-    }
-
-    public static synchronized void dump(PrintWriter pw) {
-        pw.println("FALSING LOG:");
-        if (!ENABLED) {
-            pw.println("Disabled, to enable: setprop debug.falsing_log 1");
-            pw.println();
-            return;
-        }
-        if (sInstance == null || sInstance.mLog.isEmpty()) {
-            pw.println("<empty>");
-            pw.println();
-            return;
-        }
-        for (String s : sInstance.mLog) {
-            pw.println(s);
-        }
-        pw.println();
-    }
-
-    public static synchronized void wtf(String tag, String s, Throwable here) {
-        if (!ENABLED) {
-            return;
-        }
-        e(tag, s);
-
-        Application application = ActivityThread.currentApplication();
-        String fileMessage = "";
-        if (Build.IS_DEBUGGABLE && application != null) {
-            File f = new File(application.getDataDir(), "falsing-"
-                    + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".txt");
-            PrintWriter pw = null;
-            try {
-                pw = new PrintWriter(f);
-                dump(pw);
-                pw.close();
-                fileMessage = "Log written to " + f.getAbsolutePath();
-            } catch (IOException e) {
-                Log.e(TAG, "Unable to write falsing log", e);
-            } finally {
-                if (pw != null) {
-                    pw.close();
-                }
-            }
-        } else {
-            Log.e(TAG, "Unable to write log, build must be debuggable.");
-        }
-
-        Log.wtf(TAG, tag + " " + s + "; " + fileMessage, here);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index 6961b45..1214843 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.FalsingManager;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 /**
@@ -29,10 +30,13 @@
  */
 public class FalsingManagerFake implements FalsingManager {
     private boolean mIsFalseTouch;
+    private boolean mIsFalseTap;
+    private boolean mIsFalseDoubleTap;
     private boolean mIsUnlockingDisabled;
     private boolean mIsClassiferEnabled;
     private boolean mShouldEnforceBouncer;
     private boolean mIsReportingEnabled;
+    private boolean mIsFalseRobustTap;
 
     @Override
     public void onSuccessfulUnlock() {
@@ -74,6 +78,28 @@
         return mIsFalseTouch;
     }
 
+    public void setFalseRobustTap(boolean falseRobustTap) {
+        mIsFalseRobustTap = falseRobustTap;
+    }
+
+    public void setFalseTap(boolean falseTap) {
+        mIsFalseTap = falseTap;
+    }
+
+    public void setFalseDoubleTap(boolean falseDoubleTap) {
+        mIsFalseDoubleTap = falseDoubleTap;
+    }
+
+    @Override
+    public boolean isFalseTap(boolean robustCheck) {
+        return robustCheck ? mIsFalseRobustTap : mIsFalseTap;
+    }
+
+    @Override
+    public boolean isFalseDoubleTap() {
+        return mIsFalseDoubleTap;
+    }
+
     @Override
     public void onNotificatonStopDraggingDown() {
 
@@ -236,8 +262,7 @@
     }
 
     @Override
-    public void dump(PrintWriter pw) {
-
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
deleted file mode 100644
index decaec1..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ /dev/null
@@ -1,579 +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.classifier;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.biometrics.BiometricSourceType;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
-import com.android.systemui.analytics.DataCollector;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.sensors.AsyncSensorManager;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-/**
- * When the phone is locked, listens to touch, sensor and phone events and sends them to
- * DataCollector and HumanInteractionClassifier.
- *
- * It does not collect touch events when the bouncer shows up.
- */
-public class FalsingManagerImpl implements FalsingManager {
-    private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
-
-    private static final int[] CLASSIFIER_SENSORS = new int[] {
-            Sensor.TYPE_PROXIMITY,
-    };
-
-    private static final int[] COLLECTOR_SENSORS = new int[] {
-            Sensor.TYPE_ACCELEROMETER,
-            Sensor.TYPE_GYROSCOPE,
-            Sensor.TYPE_PROXIMITY,
-            Sensor.TYPE_LIGHT,
-            Sensor.TYPE_ROTATION_VECTOR,
-    };
-    public static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
-    public static final String FALSING_SUCCESS = "falsing_success_after_attempts";
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final Context mContext;
-
-    private final SensorManager mSensorManager;
-    private final DataCollector mDataCollector;
-    private final HumanInteractionClassifier mHumanInteractionClassifier;
-    private final AccessibilityManager mAccessibilityManager;
-    private final Executor mUiBgExecutor;
-
-    private boolean mEnforceBouncer = false;
-    private boolean mBouncerOn = false;
-    private boolean mBouncerOffOnDown = false;
-    private boolean mSessionActive = false;
-    private boolean mIsTouchScreen = true;
-    private boolean mJustUnlockedWithFace = false;
-    private int mState = StatusBarState.SHADE;
-    private boolean mScreenOn;
-    private boolean mShowingAod;
-    private Runnable mPendingWtf;
-    private int mIsFalseTouchCalls;
-    private MetricsLogger mMetricsLogger;
-
-    private SensorEventListener mSensorEventListener = new SensorEventListener() {
-        @Override
-        public synchronized void onSensorChanged(SensorEvent event) {
-            mDataCollector.onSensorChanged(event);
-            mHumanInteractionClassifier.onSensorChanged(event);
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            mDataCollector.onAccuracyChanged(sensor, accuracy);
-        }
-    };
-
-    public StateListener mStatusBarStateListener = new StateListener() {
-        @Override
-        public void onStateChanged(int newState) {
-            if (FalsingLog.ENABLED) {
-                FalsingLog.i("setStatusBarState", new StringBuilder()
-                        .append("from=").append(StatusBarState.toShortString(mState))
-                        .append(" to=").append(StatusBarState.toShortString(newState))
-                        .toString());
-            }
-            mState = newState;
-            updateSessionActive();
-        }
-    };
-
-    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            updateConfiguration();
-        }
-    };
-    private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
-            new KeyguardUpdateMonitorCallback() {
-                @Override
-                public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType,
-                        boolean isStrongBiometric) {
-                    if (userId == KeyguardUpdateMonitor.getCurrentUser()
-                            && biometricSourceType == BiometricSourceType.FACE) {
-                        mJustUnlockedWithFace = true;
-                    }
-                }
-            };
-
-    FalsingManagerImpl(Context context, @UiBackground Executor uiBgExecutor) {
-        mContext = context;
-        mSensorManager = Dependency.get(AsyncSensorManager.class);
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
-        mDataCollector = DataCollector.getInstance(mContext);
-        mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
-        mUiBgExecutor = uiBgExecutor;
-        mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
-        mMetricsLogger = new MetricsLogger();
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
-                mSettingsObserver,
-                UserHandle.USER_ALL);
-
-        updateConfiguration();
-        Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mKeyguardUpdateCallback);
-    }
-
-    private void updateConfiguration() {
-        mEnforceBouncer = 0 != Settings.Secure.getInt(mContext.getContentResolver(),
-                ENFORCE_BOUNCER, 0);
-    }
-
-    private boolean shouldSessionBeActive() {
-        if (FalsingLog.ENABLED && FalsingLog.VERBOSE) {
-            FalsingLog.v("shouldBeActive", new StringBuilder()
-                    .append("enabled=").append(isEnabled() ? 1 : 0)
-                    .append(" mScreenOn=").append(mScreenOn ? 1 : 0)
-                    .append(" mState=").append(StatusBarState.toShortString(mState))
-                    .append(" mShowingAod=").append(mShowingAod ? 1 : 0)
-                    .toString()
-            );
-        }
-        return isEnabled() && mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
-    }
-
-    private boolean sessionEntrypoint() {
-        if (!mSessionActive && shouldSessionBeActive()) {
-            onSessionStart();
-            return true;
-        }
-        return false;
-    }
-
-    private void sessionExitpoint(boolean force) {
-        if (mSessionActive && (force || !shouldSessionBeActive())) {
-            mSessionActive = false;
-            if (mIsFalseTouchCalls != 0) {
-                if (FalsingLog.ENABLED) {
-                    FalsingLog.i(
-                            "isFalseTouchCalls", "Calls before failure: " + mIsFalseTouchCalls);
-                }
-                mMetricsLogger.histogram(FALSING_REMAIN_LOCKED, mIsFalseTouchCalls);
-                mIsFalseTouchCalls = 0;
-            }
-
-            // This can be expensive, and doesn't need to happen on the main thread.
-            mUiBgExecutor.execute(() -> {
-                mSensorManager.unregisterListener(mSensorEventListener);
-            });
-        }
-    }
-
-    public void updateSessionActive() {
-        if (shouldSessionBeActive()) {
-            sessionEntrypoint();
-        } else {
-            sessionExitpoint(false /* force */);
-        }
-    }
-
-    private void onSessionStart() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassifierEnabled());
-            clearPendingWtf();
-        }
-        mBouncerOn = false;
-        mSessionActive = true;
-        mJustUnlockedWithFace = false;
-        mIsFalseTouchCalls = 0;
-
-        if (mHumanInteractionClassifier.isEnabled()) {
-            registerSensors(CLASSIFIER_SENSORS);
-        }
-        if (mDataCollector.isEnabledFull()) {
-            registerSensors(COLLECTOR_SENSORS);
-        }
-        if (mDataCollector.isEnabled()) {
-            mDataCollector.onFalsingSessionStarted();
-        }
-    }
-
-    private void registerSensors(int [] sensors) {
-        for (int sensorType : sensors) {
-            Sensor s = mSensorManager.getDefaultSensor(sensorType);
-            if (s != null) {
-
-                // This can be expensive, and doesn't need to happen on the main thread.
-                mUiBgExecutor.execute(() -> {
-                    mSensorManager.registerListener(
-                            mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
-                });
-            }
-        }
-    }
-
-    public boolean isClassifierEnabled() {
-        return mHumanInteractionClassifier.isEnabled();
-    }
-
-    private boolean isEnabled() {
-        return mHumanInteractionClassifier.isEnabled() || mDataCollector.isEnabled();
-    }
-
-    public boolean isUnlockingDisabled() {
-        return mDataCollector.isUnlockingDisabled();
-    }
-    /**
-     * @return true if the classifier determined that this is not a human interacting with the phone
-     */
-    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
-        if (FalsingLog.ENABLED) {
-            // We're getting some false wtfs from touches that happen after the device went
-            // to sleep. Only report missing sessions that happen when the device is interactive.
-            if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive()
-                    && mPendingWtf == null) {
-                int enabled = isEnabled() ? 1 : 0;
-                int screenOn = mScreenOn ? 1 : 0;
-                String state = StatusBarState.toShortString(mState);
-                Throwable here = new Throwable("here");
-                FalsingLog.wLogcat("isFalseTouch", new StringBuilder()
-                        .append("Session is not active, yet there's a query for a false touch.")
-                        .append(" enabled=").append(enabled)
-                        .append(" mScreenOn=").append(screenOn)
-                        .append(" mState=").append(state)
-                        .append(". Escalating to WTF if screen does not turn on soon.")
-                        .toString());
-
-                // Unfortunately we're also getting false positives for touches that happen right
-                // after the screen turns on, but before that notification has made it to us.
-                // Unfortunately there's no good way to catch that, except to wait and see if we get
-                // the screen on notification soon.
-                mPendingWtf = () -> FalsingLog.wtf("isFalseTouch", new StringBuilder()
-                        .append("Session did not become active after query for a false touch.")
-                        .append(" enabled=").append(enabled)
-                        .append('/').append(isEnabled() ? 1 : 0)
-                        .append(" mScreenOn=").append(screenOn)
-                        .append('/').append(mScreenOn ? 1 : 0)
-                        .append(" mState=").append(state)
-                        .append('/').append(StatusBarState.toShortString(mState))
-                        .append(". Look for warnings ~1000ms earlier to see root cause.")
-                        .toString(), here);
-                mHandler.postDelayed(mPendingWtf, 1000);
-            }
-        }
-        if (ActivityManager.isRunningInUserTestHarness()) {
-            // This is a test device running UiAutomator code.
-            return false;
-        }
-        if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            // Touch exploration triggers false positives in the classifier and
-            // already sufficiently prevents false unlocks.
-            return false;
-        }
-        if (!mIsTouchScreen) {
-            // Unlocking with input devices besides the touchscreen should already be sufficiently
-            // anti-falsed.
-            return false;
-        }
-        if (mJustUnlockedWithFace) {
-            // Unlocking with face is a strong user presence signal, we can assume the user
-            // is present until the next session starts.
-            return false;
-        }
-        mIsFalseTouchCalls++;
-        boolean isFalse = mHumanInteractionClassifier.isFalseTouch();
-        if (!isFalse) {
-            if (FalsingLog.ENABLED) {
-                FalsingLog.i("isFalseTouchCalls", "Calls before success: " + mIsFalseTouchCalls);
-            }
-            mMetricsLogger.histogram(FALSING_SUCCESS, mIsFalseTouchCalls);
-            mIsFalseTouchCalls = 0;
-        }
-        return isFalse;
-    }
-
-    private void clearPendingWtf() {
-        if (mPendingWtf != null) {
-            mHandler.removeCallbacks(mPendingWtf);
-            mPendingWtf = null;
-        }
-    }
-
-
-    public boolean shouldEnforceBouncer() {
-        return mEnforceBouncer;
-    }
-
-    public void setShowingAod(boolean showingAod) {
-        mShowingAod = showingAod;
-        updateSessionActive();
-    }
-
-    public void onScreenTurningOn() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onScreenTurningOn", new StringBuilder()
-                    .append("from=").append(mScreenOn ? 1 : 0)
-                    .toString());
-            clearPendingWtf();
-        }
-        mScreenOn = true;
-        if (sessionEntrypoint()) {
-            mDataCollector.onScreenTurningOn();
-        }
-    }
-
-    public void onScreenOnFromTouch() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onScreenOnFromTouch", new StringBuilder()
-                    .append("from=").append(mScreenOn ? 1 : 0)
-                    .toString());
-        }
-        mScreenOn = true;
-        if (sessionEntrypoint()) {
-            mDataCollector.onScreenOnFromTouch();
-        }
-    }
-
-    public void onScreenOff() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onScreenOff", new StringBuilder()
-                    .append("from=").append(mScreenOn ? 1 : 0)
-                    .toString());
-        }
-        mDataCollector.onScreenOff();
-        mScreenOn = false;
-        sessionExitpoint(false /* force */);
-    }
-
-    public void onSuccessfulUnlock() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onSucccessfulUnlock", "");
-        }
-        mDataCollector.onSucccessfulUnlock();
-    }
-
-    public void onBouncerShown() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onBouncerShown", new StringBuilder()
-                    .append("from=").append(mBouncerOn ? 1 : 0)
-                    .toString());
-        }
-        if (!mBouncerOn) {
-            mBouncerOn = true;
-            mDataCollector.onBouncerShown();
-        }
-    }
-
-    public void onBouncerHidden() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onBouncerHidden", new StringBuilder()
-                    .append("from=").append(mBouncerOn ? 1 : 0)
-                    .toString());
-        }
-        if (mBouncerOn) {
-            mBouncerOn = false;
-            mDataCollector.onBouncerHidden();
-        }
-    }
-
-    public void onQsDown() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onQsDown", "");
-        }
-        mHumanInteractionClassifier.setType(Classifier.QUICK_SETTINGS);
-        mDataCollector.onQsDown();
-    }
-
-    public void setQsExpanded(boolean expanded) {
-        mDataCollector.setQsExpanded(expanded);
-    }
-
-    public void onTrackingStarted(boolean secure) {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onTrackingStarted", "");
-        }
-        mHumanInteractionClassifier.setType(secure
-                ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
-        mDataCollector.onTrackingStarted();
-    }
-
-    public void onTrackingStopped() {
-        mDataCollector.onTrackingStopped();
-    }
-
-    public void onNotificationActive() {
-        mDataCollector.onNotificationActive();
-    }
-
-    public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onNotificationDoubleTap", "accepted=" + accepted
-                    + " dx=" + dx + " dy=" + dy + " (px)");
-        }
-        mDataCollector.onNotificationDoubleTap();
-    }
-
-    public void setNotificationExpanded() {
-        mDataCollector.setNotificationExpanded();
-    }
-
-    public void onNotificatonStartDraggingDown() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onNotificatonStartDraggingDown", "");
-        }
-        mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DRAG_DOWN);
-        mDataCollector.onNotificatonStartDraggingDown();
-    }
-
-    public void onStartExpandingFromPulse() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onStartExpandingFromPulse", "");
-        }
-        mHumanInteractionClassifier.setType(Classifier.PULSE_EXPAND);
-        mDataCollector.onStartExpandingFromPulse();
-    }
-
-    public void onNotificatonStopDraggingDown() {
-        mDataCollector.onNotificatonStopDraggingDown();
-    }
-
-    public void onExpansionFromPulseStopped() {
-        mDataCollector.onExpansionFromPulseStopped();
-    }
-
-    public void onNotificationDismissed() {
-        mDataCollector.onNotificationDismissed();
-    }
-
-    public void onNotificationStartDismissing() {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onNotificationStartDismissing", "");
-        }
-        mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS);
-        mDataCollector.onNotificatonStartDismissing();
-    }
-
-    public void onNotificationStopDismissing() {
-        mDataCollector.onNotificatonStopDismissing();
-    }
-
-    public void onCameraOn() {
-        mDataCollector.onCameraOn();
-    }
-
-    public void onLeftAffordanceOn() {
-        mDataCollector.onLeftAffordanceOn();
-    }
-
-    public void onAffordanceSwipingStarted(boolean rightCorner) {
-        if (FalsingLog.ENABLED) {
-            FalsingLog.i("onAffordanceSwipingStarted", "");
-        }
-        if (rightCorner) {
-            mHumanInteractionClassifier.setType(Classifier.RIGHT_AFFORDANCE);
-        } else {
-            mHumanInteractionClassifier.setType(Classifier.LEFT_AFFORDANCE);
-        }
-        mDataCollector.onAffordanceSwipingStarted(rightCorner);
-    }
-
-    public void onAffordanceSwipingAborted() {
-        mDataCollector.onAffordanceSwipingAborted();
-    }
-
-    public void onUnlockHintStarted() {
-        mDataCollector.onUnlockHintStarted();
-    }
-
-    public void onCameraHintStarted() {
-        mDataCollector.onCameraHintStarted();
-    }
-
-    public void onLeftAffordanceHintStarted() {
-        mDataCollector.onLeftAffordanceHintStarted();
-    }
-
-    public void onTouchEvent(MotionEvent event, int width, int height) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mIsTouchScreen = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
-            // If the bouncer was not shown during the down event,
-            // we want the entire gesture going to HumanInteractionClassifier
-            mBouncerOffOnDown = !mBouncerOn;
-        }
-        if (mSessionActive) {
-            if (!mBouncerOn) {
-                // In case bouncer is "visible", but onFullyShown has not yet been called,
-                // avoid adding the event to DataCollector
-                mDataCollector.onTouchEvent(event, width, height);
-            }
-            if (mBouncerOffOnDown) {
-                mHumanInteractionClassifier.onTouchEvent(event);
-            }
-        }
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("FALSING MANAGER");
-        pw.print("classifierEnabled="); pw.println(isClassifierEnabled() ? 1 : 0);
-        pw.print("mSessionActive="); pw.println(mSessionActive ? 1 : 0);
-        pw.print("mBouncerOn="); pw.println(mSessionActive ? 1 : 0);
-        pw.print("mState="); pw.println(StatusBarState.toShortString(mState));
-        pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
-        pw.println();
-    }
-
-    @Override
-    public void cleanup() {
-        mSensorManager.unregisterListener(mSensorEventListener);
-        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
-        Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mKeyguardUpdateCallback);
-    }
-
-    public Uri reportRejectedTouch() {
-        if (mDataCollector.isEnabled()) {
-            return mDataCollector.reportRejectedTouch();
-        }
-        return null;
-    }
-
-    public boolean isReportingEnabled() {
-        return mDataCollector.isReportingEnabled();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 2c31862..814fff9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -16,13 +16,13 @@
 
 package com.android.systemui.classifier;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.SensorManager;
 import android.net.Uri;
 import android.provider.DeviceConfig;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 
 import androidx.annotation.NonNull;
 
@@ -33,7 +33,6 @@
 import com.android.systemui.classifier.brightline.FalsingDataProvider;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -53,52 +52,63 @@
 /**
  * Simple passthrough implementation of {@link FalsingManager} allowing plugins to swap in.
  *
- * {@link FalsingManagerImpl} is used when a Plugin is not loaded.
+ * {@link BrightLineFalsingManager} is used when a Plugin is not loaded.
  */
 @SysUISingleton
 public class FalsingManagerProxy implements FalsingManager, Dumpable {
 
     private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
+    private static final String DUMPABLE_TAG = "FalsingManager";
+    public static final String FALSING_REMAIN_LOCKED = "falsing_failure_after_attempts";
+    public static final String FALSING_SUCCESS = "falsing_success_after_attempts";
 
+    private final PluginManager mPluginManager;
     private final ProximitySensor mProximitySensor;
+    private final Resources mResources;
+    private final ViewConfiguration mViewConfiguration;
     private final FalsingDataProvider mFalsingDataProvider;
-    private FalsingManager mInternalFalsingManager;
-    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
     private final DeviceConfigProxy mDeviceConfig;
-    private boolean mBrightlineEnabled;
     private final DockManager mDockManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private Executor mUiBgExecutor;
+    private final DumpManager mDumpManager;
     private final StatusBarStateController mStatusBarStateController;
+    final PluginListener<FalsingPlugin> mPluginListener;
+
+    private FalsingManager mInternalFalsingManager;
+
+    private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+            properties -> onDeviceConfigPropertiesChanged(properties.getNamespace());
 
     @Inject
-    FalsingManagerProxy(Context context, PluginManager pluginManager, @Main Executor executor,
+    FalsingManagerProxy(PluginManager pluginManager, @Main Executor executor,
             ProximitySensor proximitySensor,
             DeviceConfigProxy deviceConfig, DockManager dockManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DumpManager dumpManager,
-            @UiBackground Executor uiBgExecutor,
             StatusBarStateController statusBarStateController,
+            @Main Resources resources,
+            ViewConfiguration viewConfiguration,
             FalsingDataProvider falsingDataProvider) {
+        mPluginManager = pluginManager;
         mProximitySensor = proximitySensor;
         mDockManager = dockManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mUiBgExecutor = uiBgExecutor;
+        mDumpManager = dumpManager;
         mStatusBarStateController = statusBarStateController;
+        mResources = resources;
+        mViewConfiguration = viewConfiguration;
         mFalsingDataProvider = falsingDataProvider;
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
         mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
         mDeviceConfig = deviceConfig;
-        mDeviceConfigListener =
-                properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace());
-        setupFalsingManager(context);
+        setupFalsingManager();
         mDeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 executor,
                 mDeviceConfigListener
         );
 
-        final PluginListener<FalsingPlugin> mPluginListener = new PluginListener<FalsingPlugin>() {
+        mPluginListener = new PluginListener<FalsingPlugin>() {
             public void onPluginConnected(FalsingPlugin plugin, Context context) {
                 FalsingManager pluginFalsingManager = plugin.getFalsingManager(context);
                 if (pluginFalsingManager != null) {
@@ -108,49 +118,42 @@
             }
 
             public void onPluginDisconnected(FalsingPlugin plugin) {
-                mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
+                setupFalsingManager();
             }
         };
 
-        pluginManager.addPluginListener(mPluginListener, FalsingPlugin.class);
+        mPluginManager.addPluginListener(mPluginListener, FalsingPlugin.class);
 
-        dumpManager.registerDumpable("FalsingManager", this);
+        mDumpManager.registerDumpable(DUMPABLE_TAG, this);
     }
 
-    private void onDeviceConfigPropertiesChanged(Context context, String namespace) {
+    private void onDeviceConfigPropertiesChanged(String namespace) {
         if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(namespace)) {
             return;
         }
 
-        setupFalsingManager(context);
+        setupFalsingManager();
     }
 
     /**
-     * Chooses the FalsingManager implementation.
+     * Setup the FalsingManager implementation.
+     *
+     * If multiple implementations are available, this is where the choice is made.
      */
-    private void setupFalsingManager(Context context) {
-        boolean brightlineEnabled = mDeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_MANAGER_ENABLED, true);
-        if (brightlineEnabled == mBrightlineEnabled && mInternalFalsingManager != null) {
-            return;
-        }
-        mBrightlineEnabled = brightlineEnabled;
-
+    private void setupFalsingManager() {
         if (mInternalFalsingManager != null) {
             mInternalFalsingManager.cleanup();
         }
-        if (!brightlineEnabled) {
-            mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
-        } else {
-            mInternalFalsingManager = new BrightLineFalsingManager(
-                    mFalsingDataProvider,
-                    mKeyguardUpdateMonitor,
-                    mProximitySensor,
-                    mDeviceConfig,
-                    mDockManager,
-                    mStatusBarStateController
-            );
-        }
+        mInternalFalsingManager = new BrightLineFalsingManager(
+                mFalsingDataProvider,
+                mKeyguardUpdateMonitor,
+                mProximitySensor,
+                mDeviceConfig,
+                mResources,
+                mViewConfiguration,
+                mDockManager,
+                mStatusBarStateController
+        );
     }
 
     /**
@@ -191,6 +194,17 @@
         return mInternalFalsingManager.isFalseTouch(interactionType);
     }
 
+
+    @Override
+    public boolean isFalseTap(boolean robustCheck) {
+        return mInternalFalsingManager.isFalseTap(robustCheck);
+    }
+
+    @Override
+    public boolean isFalseDoubleTap() {
+        return mInternalFalsingManager.isFalseDoubleTap();
+    }
+
     @Override
     public void onNotificatonStopDraggingDown() {
         mInternalFalsingManager.onNotificatonStartDraggingDown();
@@ -338,17 +352,14 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        mInternalFalsingManager.dump(pw);
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        mInternalFalsingManager.dump(pw);
+        mInternalFalsingManager.dump(fd, pw, args);
     }
 
     @Override
     public void cleanup() {
         mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+        mPluginManager.removePluginListener(mPluginListener);
+        mDumpManager.unregisterDumpable(DUMPABLE_TAG);
         mInternalFalsingManager.cleanup();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java
deleted file mode 100644
index 11388fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/GestureClassifier.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * An abstract class for classifiers which classify the whole gesture (all the strokes which
- * occurred from DOWN event to UP/CANCEL event)
- */
-public abstract class GestureClassifier extends Classifier {
-
-    /**
-     * @param type the type of action for which this method is called
-     * @return a non-negative value which is used to determine whether the most recent gesture is a
-     *         false interaction; the bigger the value the greater the chance that this a false
-     *         interaction.
-     */
-    public abstract float getFalseTouchEvaluation(int type);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
deleted file mode 100644
index 4c64711..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryEvaluator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.os.SystemClock;
-
-import java.util.ArrayList;
-
-/**
- * Holds the evaluations for ended strokes and gestures. These values are decreased through time.
- */
-public class HistoryEvaluator {
-    private static final float INTERVAL = 50.0f;
-    private static final float HISTORY_FACTOR = 0.9f;
-    private static final float EPSILON = 1e-5f;
-
-    private final ArrayList<Data> mStrokes = new ArrayList<>();
-    private final ArrayList<Data> mGestureWeights = new ArrayList<>();
-    private long mLastUpdate;
-
-    public HistoryEvaluator() {
-        mLastUpdate = SystemClock.elapsedRealtime();
-    }
-
-    public void addStroke(float evaluation) {
-        decayValue();
-        mStrokes.add(new Data(evaluation));
-    }
-
-    public void addGesture(float evaluation) {
-        decayValue();
-        mGestureWeights.add(new Data(evaluation));
-    }
-
-    /**
-     * Calculates the weighted average of strokes and adds to it the weighted average of gestures
-     */
-    public float getEvaluation() {
-        return weightedAverage(mStrokes) + weightedAverage(mGestureWeights);
-    }
-
-    private float weightedAverage(ArrayList<Data> list) {
-        float sumValue = 0.0f;
-        float sumWeight = 0.0f;
-        int size = list.size();
-        for (int i = 0; i < size; i++) {
-            Data data = list.get(i);
-            sumValue += data.evaluation * data.weight;
-            sumWeight += data.weight;
-        }
-
-        if (sumWeight == 0.0f) {
-            return 0.0f;
-        }
-
-        return sumValue / sumWeight;
-    }
-
-    private void decayValue() {
-        long time = SystemClock.elapsedRealtime();
-
-        if (time <= mLastUpdate) {
-            return;
-        }
-
-        // All weights are multiplied by HISTORY_FACTOR after each INTERVAL milliseconds.
-        float factor = (float) Math.pow(HISTORY_FACTOR, (time - mLastUpdate) / INTERVAL);
-
-        decayValue(mStrokes, factor);
-        decayValue(mGestureWeights, factor);
-        mLastUpdate = time;
-    }
-
-    private void decayValue(ArrayList<Data> list, float factor) {
-        int size = list.size();
-        for (int i = 0; i < size; i++) {
-            list.get(i).weight *= factor;
-        }
-
-        // Removing evaluations with such small weights that they do not matter anymore
-        while (!list.isEmpty() && isZero(list.get(0).weight)) {
-            list.remove(0);
-        }
-    }
-
-    private boolean isZero(float x) {
-        return x <= EPSILON && x >= -EPSILON;
-    }
-
-    /**
-     * For each stroke it holds its initial value and the current weight. Initially the
-     * weight is set to 1.0
-     */
-    private static class Data {
-        public float evaluation;
-        public float weight;
-
-        public Data(float evaluation) {
-            this.evaluation = evaluation;
-            weight = 1.0f;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
deleted file mode 100644
index 86dccb2..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.SensorEvent;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-
-import com.android.systemui.R;
-
-import java.util.ArrayDeque;
-
-/**
- * An classifier trying to determine whether it is a human interacting with the phone or not.
- */
-public class HumanInteractionClassifier extends Classifier {
-    private static final String HIC_ENABLE = "HIC_enable";
-    private static final float FINGER_DISTANCE = 0.1f;
-
-    private static HumanInteractionClassifier sInstance = null;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final Context mContext;
-
-    private final StrokeClassifier[] mStrokeClassifiers;
-    private final GestureClassifier[] mGestureClassifiers;
-    private final ArrayDeque<MotionEvent> mBufferedEvents = new ArrayDeque<>();
-    private final HistoryEvaluator mHistoryEvaluator;
-    private final float mDpi;
-
-    private boolean mEnableClassifier = false;
-    private int mCurrentType = Classifier.GENERIC;
-
-    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            updateConfiguration();
-        }
-    };
-
-    private HumanInteractionClassifier(Context context) {
-        mContext = context;
-        DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
-
-        // If the phone is rotated to landscape, the calculations would be wrong if xdpi and ydpi
-        // were to be used separately. Due negligible differences in xdpi and ydpi we can just
-        // take the average.
-        // Note that xdpi and ydpi are the physical pixels per inch and are not affected by scaling.
-        mDpi = (displayMetrics.xdpi + displayMetrics.ydpi) / 2.0f;
-        mClassifierData = new ClassifierData(mDpi);
-        mHistoryEvaluator = new HistoryEvaluator();
-
-        mStrokeClassifiers = new StrokeClassifier[]{
-                new AnglesClassifier(mClassifierData),
-                new SpeedClassifier(mClassifierData),
-                new DurationCountClassifier(mClassifierData),
-                new EndPointRatioClassifier(mClassifierData),
-                new EndPointLengthClassifier(mClassifierData),
-                new AccelerationClassifier(mClassifierData),
-                new SpeedAnglesClassifier(mClassifierData),
-                new LengthCountClassifier(mClassifierData),
-                new DirectionClassifier(mClassifierData),
-        };
-
-        mGestureClassifiers = new GestureClassifier[] {
-                new PointerCountClassifier(mClassifierData),
-                new ProximityClassifier(mClassifierData)
-        };
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(HIC_ENABLE), false,
-                mSettingsObserver,
-                UserHandle.USER_ALL);
-
-        updateConfiguration();
-    }
-
-    public static HumanInteractionClassifier getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new HumanInteractionClassifier(context);
-        }
-        return sInstance;
-    }
-
-    private void updateConfiguration() {
-        boolean defaultValue = mContext.getResources().getBoolean(
-                R.bool.config_lockscreenAntiFalsingClassifierEnabled);
-
-        mEnableClassifier = 0 != Settings.Global.getInt(
-                mContext.getContentResolver(),
-                HIC_ENABLE, defaultValue ? 1 : 0);
-    }
-
-    public void setType(int type) {
-        mCurrentType = type;
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        if (!mEnableClassifier) {
-            return;
-        }
-
-        // If the user is dragging down the notification, they might want to drag it down
-        // enough to see the content, read it for a while and then lift the finger to open
-        // the notification. This kind of motion scores very bad in the Classifier so the
-        // MotionEvents which are close to the current position of the finger are not
-        // sent to the classifiers until the finger moves far enough. When the finger if lifted
-        // up, the last MotionEvent which was far enough from the finger is set as the final
-        // MotionEvent and sent to the Classifiers.
-        if (mCurrentType == Classifier.NOTIFICATION_DRAG_DOWN
-                || mCurrentType == Classifier.PULSE_EXPAND) {
-            mBufferedEvents.add(MotionEvent.obtain(event));
-            Point pointEnd = new Point(event.getX() / mDpi, event.getY() / mDpi);
-
-            while (pointEnd.dist(new Point(mBufferedEvents.getFirst().getX() / mDpi,
-                    mBufferedEvents.getFirst().getY() / mDpi)) > FINGER_DISTANCE) {
-                addTouchEvent(mBufferedEvents.getFirst());
-                mBufferedEvents.remove();
-            }
-
-            int action = event.getActionMasked();
-            if (action == MotionEvent.ACTION_UP) {
-                mBufferedEvents.getFirst().setAction(MotionEvent.ACTION_UP);
-                addTouchEvent(mBufferedEvents.getFirst());
-                mBufferedEvents.clear();
-            }
-        } else {
-            addTouchEvent(event);
-        }
-    }
-
-    private void addTouchEvent(MotionEvent event) {
-        if (!mClassifierData.update(event)) {
-            return;
-        }
-
-        for (StrokeClassifier c : mStrokeClassifiers) {
-            c.onTouchEvent(event);
-        }
-
-        for (GestureClassifier c : mGestureClassifiers) {
-            c.onTouchEvent(event);
-        }
-
-        int size = mClassifierData.getEndingStrokes().size();
-        for (int i = 0; i < size; i++) {
-            Stroke stroke = mClassifierData.getEndingStrokes().get(i);
-            float evaluation = 0.0f;
-            StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("stroke") : null;
-            for (StrokeClassifier c : mStrokeClassifiers) {
-                float e = c.getFalseTouchEvaluation(mCurrentType, stroke);
-                if (FalsingLog.ENABLED) {
-                    String tag = c.getTag();
-                    sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
-                }
-                evaluation += e;
-            }
-
-            if (FalsingLog.ENABLED) {
-                FalsingLog.i(" addTouchEvent", sb.toString());
-            }
-            mHistoryEvaluator.addStroke(evaluation);
-        }
-
-        int action = event.getActionMasked();
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            float evaluation = 0.0f;
-            StringBuilder sb = FalsingLog.ENABLED ? new StringBuilder("gesture") : null;
-            for (GestureClassifier c : mGestureClassifiers) {
-                float e = c.getFalseTouchEvaluation(mCurrentType);
-                if (FalsingLog.ENABLED) {
-                    String tag = c.getTag();
-                    sb.append(" ").append(e >= 1f ? tag : tag.toLowerCase()).append("=").append(e);
-                }
-                evaluation += e;
-            }
-            if (FalsingLog.ENABLED) {
-                FalsingLog.i(" addTouchEvent", sb.toString());
-            }
-            mHistoryEvaluator.addGesture(evaluation);
-            setType(Classifier.GENERIC);
-        }
-
-        mClassifierData.cleanUp(event);
-    }
-
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        for (Classifier c : mStrokeClassifiers) {
-            c.onSensorChanged(event);
-        }
-
-        for (Classifier c : mGestureClassifiers) {
-            c.onSensorChanged(event);
-        }
-    }
-
-    public boolean isFalseTouch() {
-        if (mEnableClassifier) {
-            float evaluation = mHistoryEvaluator.getEvaluation();
-            boolean result = evaluation >= 5.0f;
-            if (FalsingLog.ENABLED) {
-                FalsingLog.i("isFalseTouch", new StringBuilder()
-                        .append("eval=").append(evaluation).append(" result=")
-                        .append(result ? 1 : 0).toString());
-            }
-            return result;
-        }
-        return false;
-    }
-
-    public boolean isEnabled() {
-        return mEnableClassifier;
-    }
-
-    @Override
-    public String getTag() {
-        return "HIC";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
deleted file mode 100644
index 53678a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountClassifier.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the ratio between the length of the stroke and its number of
- * points. The number of points is subtracted by 2 because the UP event comes in with some delay
- * and it should not influence the ratio and also strokes which are long and have a small number
- * of points are punished more (these kind of strokes are usually bad ones and they tend to score
- * well in other classifiers).
- */
-public class LengthCountClassifier extends StrokeClassifier {
-    public LengthCountClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "LEN_CNT";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        return LengthCountEvaluator.evaluate(stroke.getTotalLength()
-                / Math.max(1.0f, stroke.getCount() - 2));
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java
deleted file mode 100644
index dac7a6f..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/LengthCountEvaluator.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier which looks at the ratio between the length of the stroke and its number of
- * points.
- */
-public class LengthCountEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 0.09) evaluation++;
-        if (value < 0.05) evaluation++;
-        if (value < 0.02) evaluation++;
-        if (value > 0.6) evaluation++;
-        if (value > 0.9) evaluation++;
-        if (value > 1.2) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Point.java b/packages/SystemUI/src/com/android/systemui/classifier/Point.java
deleted file mode 100644
index f3dc2be..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/Point.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class Point {
-    public float x;
-    public float y;
-    public long timeOffsetNano;
-
-    public Point(float x, float y) {
-        this.x = x;
-        this.y = y;
-        this.timeOffsetNano = 0;
-    }
-
-    public Point(float x, float y, long timeOffsetNano) {
-        this.x = x;
-        this.y = y;
-        this.timeOffsetNano = timeOffsetNano;
-    }
-
-    public boolean equals(Point p) {
-        return x == p.x && y == p.y;
-    }
-
-    public float dist(Point a) {
-        return (float) Math.hypot(a.x - x, a.y - y);
-    }
-
-    /**
-     * Calculates the cross product of vec(this, a) and vec(this, b) where vec(x,y) is the
-     * vector from point x to point y
-     */
-    public float crossProduct(Point a, Point b) {
-        return (a.x - x) * (b.y - y) - (a.y - y) * (b.x - x);
-    }
-
-    /**
-     * Calculates the dot product of vec(this, a) and vec(this, b) where vec(x,y) is the
-     * vector from point x to point y
-     */
-    public float dotProduct(Point a, Point b) {
-        return (a.x - x) * (b.x - x) + (a.y - y) * (b.y - y);
-    }
-
-    /**
-     * Calculates the angle in radians created by points (a, this, b). If any two of these points
-     * are the same, the method will return 0.0f
-     *
-     * @return the angle in radians
-     */
-    public float getAngle(Point a, Point b) {
-        float dist1 = dist(a);
-        float dist2 = dist(b);
-
-        if (dist1 == 0.0f || dist2 == 0.0f) {
-            return 0.0f;
-        }
-
-        float crossProduct = crossProduct(a, b);
-        float dotProduct = dotProduct(a, b);
-        float cos = Math.min(1.0f, Math.max(-1.0f, dotProduct / dist1 / dist2));
-        float angle = (float) Math.acos(cos);
-        if (crossProduct < 0.0) {
-            angle = 2.0f * (float) Math.PI - angle;
-        }
-        return angle;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
deleted file mode 100644
index 136c433..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.view.MotionEvent;
-
-/**
- * A classifier which looks at the total number of traces in the whole gesture.
- */
-public class PointerCountClassifier extends GestureClassifier {
-    private int mCount;
-
-    public PointerCountClassifier(ClassifierData classifierData) {
-        mCount = 0;
-    }
-
-    @Override
-    public String getTag() {
-        return "PTR_CNT";
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mCount = 1;
-        }
-
-        if (action == MotionEvent.ACTION_POINTER_DOWN) {
-            ++mCount;
-        }
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type) {
-        return PointerCountEvaluator.evaluate(mCount);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
deleted file mode 100644
index 62adfc8..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.view.MotionEvent;
-
-/**
- * A classifier which looks at the proximity sensor during the gesture. It calculates the percentage
- * the proximity sensor showing the near state during the whole gesture
- */
-public class ProximityClassifier extends GestureClassifier {
-    private long mGestureStartTimeNano;
-    private long mNearStartTimeNano;
-    private long mNearDuration;
-    private boolean mNear;
-    private float mAverageNear;
-
-    public ProximityClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "PROX";
-    }
-
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
-            update(event.values[0] < event.sensor.getMaximumRange(), event.timestamp);
-        }
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mGestureStartTimeNano = event.getEventTimeNano();
-            mNearStartTimeNano = event.getEventTimeNano();
-            mNearDuration = 0;
-        }
-
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            update(mNear, event.getEventTimeNano());
-            long duration = event.getEventTimeNano() - mGestureStartTimeNano;
-
-            if (duration == 0) {
-                mAverageNear = mNear ? 1.0f : 0.0f;
-            } else {
-                mAverageNear = (float) mNearDuration / (float) duration;
-            }
-        }
-    }
-
-
-    /**
-     * @param near is the sensor showing the near state right now
-     * @param timestampNano time of this event in nanoseconds
-     */
-    private void update(boolean near, long timestampNano) {
-        // This if is necessary because MotionEvents and SensorEvents do not come in
-        // chronological order
-        if (timestampNano > mNearStartTimeNano) {
-            // if the state before was near then add the difference of the current time and
-            // mNearStartTimeNano to mNearDuration.
-            if (mNear) {
-                mNearDuration += timestampNano - mNearStartTimeNano;
-            }
-
-            // if the new state is near, set mNearStartTimeNano equal to this moment.
-            if (near) {
-                mNearStartTimeNano = timestampNano;
-            }
-        }
-        mNear = near;
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type) {
-        return ProximityEvaluator.evaluate(mAverageNear, type);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java
deleted file mode 100644
index 91002bf..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityEvaluator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class ProximityEvaluator {
-    public static float evaluate(float value, int type) {
-        float evaluation = 0.0f;
-        float threshold = 0.1f;
-        if (type == Classifier.QUICK_SETTINGS) {
-            threshold = 1.0f;
-        }
-        if (value >= threshold) evaluation += 2.0;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
deleted file mode 100644
index 66f0cf6..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import android.os.Build;
-import android.os.SystemProperties;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A classifier which for each point from a stroke, it creates a point on plane with coordinates
- * (timeOffsetNano, distanceCoveredUpToThisPoint) (scaled by DURATION_SCALE and LENGTH_SCALE)
- * and then it calculates the angle variance of these points like the class
- * {@link AnglesClassifier} (without splitting it into two parts). The classifier ignores
- * the last point of a stroke because the UP event comes in with some delay and this ruins the
- * smoothness of this curve. Additionally, the classifier classifies calculates the percentage of
- * angles which value is in [PI - ANGLE_DEVIATION, 2* PI) interval. The reason why the classifier
- * does that is because the speed of a good stroke is most often increases, so most of these angels
- * should be in this interval.
- */
-public class SpeedAnglesClassifier extends StrokeClassifier {
-    public static final boolean VERBOSE = SystemProperties.getBoolean("debug.falsing_log.spd_ang",
-            Build.IS_DEBUGGABLE);
-    public static final String TAG = "SPD_ANG";
-
-    private HashMap<Stroke, Data> mStrokeMap = new HashMap<>();
-
-    public SpeedAnglesClassifier(ClassifierData classifierData) {
-        mClassifierData = classifierData;
-    }
-
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mStrokeMap.clear();
-        }
-
-        for (int i = 0; i < event.getPointerCount(); i++) {
-            Stroke stroke = mClassifierData.getStroke(event.getPointerId(i));
-
-            if (mStrokeMap.get(stroke) == null) {
-                mStrokeMap.put(stroke, new Data());
-            }
-
-            if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL
-                    && !(action == MotionEvent.ACTION_POINTER_UP && i == event.getActionIndex())) {
-                mStrokeMap.get(stroke).addPoint(
-                        stroke.getPoints().get(stroke.getPoints().size() - 1));
-            }
-        }
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        Data data = mStrokeMap.get(stroke);
-        return SpeedVarianceEvaluator.evaluate(data.getAnglesVariance())
-                + SpeedAnglesPercentageEvaluator.evaluate(data.getAnglesPercentage());
-    }
-
-    private static class Data {
-        private final float DURATION_SCALE = 1e8f;
-        private final float LENGTH_SCALE = 1.0f;
-        private final float ANGLE_DEVIATION = (float) Math.PI / 10.0f;
-
-        private List<Point> mLastThreePoints = new ArrayList<>();
-        private Point mPreviousPoint;
-        private float mPreviousAngle;
-        private float mSumSquares;
-        private float mSum;
-        private float mCount;
-        private float mDist;
-        private float mAnglesCount;
-        private float mAcceleratingAngles;
-
-        public Data() {
-            mPreviousPoint = null;
-            mPreviousAngle = (float) Math.PI;
-            mSumSquares = 0.0f;
-            mSum = 0.0f;
-            mCount = 1.0f;
-            mDist = 0.0f;
-            mAnglesCount = mAcceleratingAngles = 0.0f;
-        }
-
-        public void addPoint(Point point) {
-            if (mPreviousPoint != null) {
-                mDist += mPreviousPoint.dist(point);
-            }
-
-            mPreviousPoint = point;
-            Point speedPoint = new Point((float) point.timeOffsetNano / DURATION_SCALE,
-                    mDist / LENGTH_SCALE);
-
-            // Checking if the added point is different than the previously added point
-            // Repetitions are being ignored so that proper angles are calculated.
-            if (mLastThreePoints.isEmpty()
-                    || !mLastThreePoints.get(mLastThreePoints.size() - 1).equals(speedPoint)) {
-                mLastThreePoints.add(speedPoint);
-                if (mLastThreePoints.size() == 4) {
-                    mLastThreePoints.remove(0);
-
-                    float angle = mLastThreePoints.get(1).getAngle(mLastThreePoints.get(0),
-                            mLastThreePoints.get(2));
-
-                    mAnglesCount++;
-                    if (angle >= (float) Math.PI - ANGLE_DEVIATION) {
-                        mAcceleratingAngles++;
-                    }
-
-                    float difference = angle - mPreviousAngle;
-                    mSum += difference;
-                    mSumSquares += difference * difference;
-                    mCount += 1.0;
-                    mPreviousAngle = angle;
-                }
-            }
-        }
-
-        public float getAnglesVariance() {
-            final float v = mSumSquares / mCount - (mSum / mCount) * (mSum / mCount);
-            if (VERBOSE) {
-                FalsingLog.i(TAG, "getAnglesVariance: sum^2=" + mSumSquares
-                        + " count=" + mCount + " result=" + v);
-            }
-            return v;
-        }
-
-        public float getAnglesPercentage() {
-            if (mAnglesCount == 0.0f) {
-                return 1.0f;
-            }
-            final float v = (mAcceleratingAngles) / mAnglesCount;
-            if (VERBOSE) {
-                FalsingLog.i(TAG, "getAnglesPercentage: angles=" + mAcceleratingAngles
-                        + " count=" + mAnglesCount + " result=" + v);
-            }
-            return v;
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
deleted file mode 100644
index d50d406..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesPercentageEvaluator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class SpeedAnglesPercentageEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 1.00) evaluation++;
-        if (value < 0.90) evaluation++;
-        if (value < 0.70) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
deleted file mode 100644
index 01fcc37..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedClassifier.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * A classifier that looks at the speed of the stroke. It calculates the speed of a stroke in
- * inches per second.
- */
-public class SpeedClassifier extends StrokeClassifier {
-    private final float NANOS_TO_SECONDS = 1e9f;
-
-    public SpeedClassifier(ClassifierData classifierData) {
-    }
-
-    @Override
-    public String getTag() {
-        return "SPD";
-    }
-
-    @Override
-    public float getFalseTouchEvaluation(int type, Stroke stroke) {
-        float duration = (float) stroke.getDurationNanos() / NANOS_TO_SECONDS;
-        if (duration == 0.0f) {
-            return SpeedEvaluator.evaluate(0.0f);
-        }
-        return SpeedEvaluator.evaluate(stroke.getTotalLength() / duration);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
deleted file mode 100644
index afd8d01..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class SpeedEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value < 4.0) evaluation++;
-        if (value < 2.2) evaluation++;
-        if (value > 35.0) evaluation++;
-        if (value > 50.0) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
deleted file mode 100644
index e34f222..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedRatioEvaluator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class SpeedRatioEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value == 0) return 0;
-        if (value <= 1.0) evaluation++;
-        if (value <= 0.5) evaluation++;
-        if (value > 9.0) evaluation++;
-        if (value > 18.0) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
deleted file mode 100644
index 48b1b6e..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedVarianceEvaluator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-public class SpeedVarianceEvaluator {
-    public static float evaluate(float value) {
-        float evaluation = 0.0f;
-        if (value > 0.06) evaluation++;
-        if (value > 0.15) evaluation++;
-        if (value > 0.3) evaluation++;
-        if (value > 0.6) evaluation++;
-        return evaluation;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java b/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
deleted file mode 100644
index 977a2d0..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/Stroke.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-import java.util.ArrayList;
-
-/**
- * Contains data about a stroke (a single trace, all the events from a given id from the
- * DOWN/POINTER_DOWN event till the UP/POINTER_UP/CANCEL event.)
- */
-public class Stroke {
-    private final float NANOS_TO_SECONDS = 1e9f;
-
-    private ArrayList<Point> mPoints = new ArrayList<>();
-    private long mStartTimeNano;
-    private long mEndTimeNano;
-    private float mLength;
-    private final float mDpi;
-
-    public Stroke(long eventTimeNano, float dpi) {
-        mDpi = dpi;
-        mStartTimeNano = mEndTimeNano = eventTimeNano;
-    }
-
-    public void addPoint(float x, float y, long eventTimeNano) {
-        mEndTimeNano = eventTimeNano;
-        Point point = new Point(x / mDpi, y / mDpi, eventTimeNano - mStartTimeNano);
-        if (!mPoints.isEmpty()) {
-            mLength += mPoints.get(mPoints.size() - 1).dist(point);
-        }
-        mPoints.add(point);
-    }
-
-    public int getCount() {
-        return mPoints.size();
-    }
-
-    public float getTotalLength() {
-        return mLength;
-    }
-
-    public float getEndPointLength() {
-        return mPoints.get(0).dist(mPoints.get(mPoints.size() - 1));
-    }
-
-    public long getDurationNanos() {
-        return mEndTimeNano - mStartTimeNano;
-    }
-
-    public float getDurationSeconds() {
-        return (float) getDurationNanos() / NANOS_TO_SECONDS;
-    }
-
-    public ArrayList<Point> getPoints() {
-        return mPoints;
-    }
-
-    public long getLastEventTimeNano() {
-        if (mPoints.isEmpty()) {
-            return mStartTimeNano;
-        }
-
-        return mPoints.get(mPoints.size() - 1).timeOffsetNano;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java
deleted file mode 100644
index 5da392f..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/StrokeClassifier.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 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.classifier;
-
-/**
- * An abstract class for classifiers which classify each stroke separately.
- */
-public abstract class StrokeClassifier extends Classifier {
-
-    /**
-     * @param type the type of action for which this method is called
-     * @param stroke the stroke for which the evaluation will be calculated
-     * @return a non-negative value which is used to determine whether this a false touch; the
-     *         bigger the value the greater the chance that this a false touch
-     */
-    public abstract float getFalseTouchEvaluation(int type, Stroke stroke);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 9d847ca..334102d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -16,29 +16,37 @@
 
 package com.android.systemui.classifier.brightline;
 
-import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED;
-import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS;
+import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_REMAIN_LOCKED;
+import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
 
 import android.app.ActivityManager;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.net.Uri;
 import android.os.Build;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.R;
 import com.android.systemui.classifier.Classifier;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.NotificationTapHelper;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -64,6 +72,8 @@
     private final ProximitySensor mProximitySensor;
     private final DockManager mDockManager;
     private final StatusBarStateController mStatusBarStateController;
+    private final SingleTapClassifier mSingleTapClassifier;
+    private final DoubleTapClassifier mDoubleTapClassifier;
     private boolean mSessionStarted;
     private MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
@@ -106,8 +116,9 @@
 
     public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
             KeyguardUpdateMonitor keyguardUpdateMonitor, ProximitySensor proximitySensor,
-            DeviceConfigProxy deviceConfigProxy,
-            DockManager dockManager, StatusBarStateController statusBarStateController) {
+            DeviceConfigProxy deviceConfigProxy, @Main Resources resources,
+            ViewConfiguration viewConfiguration, DockManager dockManager,
+            StatusBarStateController statusBarStateController) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDataProvider = falsingDataProvider;
         mProximitySensor = proximitySensor;
@@ -129,6 +140,12 @@
         mClassifiers.add(distanceClassifier);
         mClassifiers.add(proximityClassifier);
         mClassifiers.add(new ZigZagClassifier(mDataProvider, deviceConfigProxy));
+
+        mSingleTapClassifier = new SingleTapClassifier(
+                mDataProvider, viewConfiguration.getScaledTouchSlop());
+        mDoubleTapClassifier = new DoubleTapClassifier(mDataProvider, mSingleTapClassifier,
+                resources.getDimension(R.dimen.double_tap_slop),
+                NotificationTapHelper.DOUBLE_TAP_TIMEOUT_MS);
     }
 
     private void registerSensors() {
@@ -237,6 +254,36 @@
     }
 
     @Override
+    public boolean isFalseTap(boolean robustCheck) {
+        if (!mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents())) {
+            logInfo(String.format(
+                    (Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
+            String reason = mSingleTapClassifier.getReason();
+            if (reason != null) {
+                logInfo(reason);
+            }
+            return true;
+        }
+
+        // TODO(b/172655679): we always reject single-taps when doing a robust check for now.
+        return robustCheck;
+    }
+
+    @Override
+    public boolean isFalseDoubleTap() {
+        boolean result = mDoubleTapClassifier.isFalseTouch();
+        if (result) {
+            logInfo(String.format(
+                    (Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
+            String reason = mDoubleTapClassifier.getReason();
+            if (reason != null) {
+                logInfo(reason);
+            }
+        }
+        return result;
+    }
+
+    @Override
     public void onTouchEvent(MotionEvent motionEvent, int width, int height) {
         // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
         // make these calls.
@@ -413,7 +460,7 @@
     }
 
     @Override
-    public void dump(PrintWriter pw) {
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println("BRIGHTLINE FALSING MANAGER");
         ipw.print("classifierEnabled=");
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java
new file mode 100644
index 0000000..d3af1c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DoubleTapClassifier.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.brightline;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Returns a false touch if the most two recent gestures are not taps or are too far apart.
+ */
+public class DoubleTapClassifier extends FalsingClassifier {
+
+    private final SingleTapClassifier mSingleTapClassifier;
+    private final float mDoubleTapSlop;
+    private final long mDoubleTapTimeMs;
+
+    private StringBuilder mReason = new StringBuilder();
+
+    DoubleTapClassifier(FalsingDataProvider dataProvider, SingleTapClassifier singleTapClassifier,
+            float doubleTapSlop, long doubleTapTimeMs) {
+        super(dataProvider);
+        mSingleTapClassifier = singleTapClassifier;
+        mDoubleTapSlop = doubleTapSlop;
+        mDoubleTapTimeMs = doubleTapTimeMs;
+    }
+
+    @Override
+    boolean isFalseTouch() {
+        List<MotionEvent> secondTapEvents = getRecentMotionEvents();
+        Queue<? extends List<MotionEvent>> historicalEvents = getHistoricalEvents();
+        List<MotionEvent> firstTapEvents = historicalEvents.peek();
+
+        mReason = new StringBuilder();
+
+        if (firstTapEvents == null) {
+            mReason.append("Only one gesture recorded");
+            return true;
+        }
+
+        return !isDoubleTap(firstTapEvents, secondTapEvents, mReason);
+    }
+
+    /** Returns true if the two supplied lists of {@link MotionEvent}s look like a double-tap. */
+    public boolean isDoubleTap(List<MotionEvent> firstEvents, List<MotionEvent> secondEvents,
+            StringBuilder reason) {
+
+        if (!mSingleTapClassifier.isTap(firstEvents)) {
+            reason.append("First gesture is not a tap. ").append(mSingleTapClassifier.getReason());
+            return false;
+        }
+
+        if (!mSingleTapClassifier.isTap(secondEvents)) {
+            reason.append("Second gesture is not a tap. ").append(mSingleTapClassifier.getReason());
+            return false;
+        }
+
+        MotionEvent firstFinalEvent = firstEvents.get(firstEvents.size() - 1);
+        MotionEvent secondFinalEvent = secondEvents.get(secondEvents.size() - 1);
+
+        long dt = secondFinalEvent.getEventTime() - firstFinalEvent.getEventTime();
+
+        if (dt > mDoubleTapTimeMs) {
+            reason.append("Time between taps too large: ").append(dt).append("ms");
+            return false;
+        }
+
+        if (Math.abs(firstFinalEvent.getX() - secondFinalEvent.getX()) >= mDoubleTapSlop) {
+            reason.append("Delta X between taps too large:")
+                    .append(Math.abs(firstFinalEvent.getX() - secondFinalEvent.getX()))
+                    .append(" vs ")
+                    .append(mDoubleTapSlop);
+            return false;
+        }
+
+        if (Math.abs(firstFinalEvent.getY() - secondFinalEvent.getY()) >= mDoubleTapSlop) {
+            reason.append("Delta Y between taps too large:")
+                    .append(Math.abs(firstFinalEvent.getY() - secondFinalEvent.getY()))
+                    .append(" vs ")
+                    .append(mDoubleTapSlop);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    String getReason() {
+        return mReason.toString();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
index 85e95a6..ed417b3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
@@ -22,6 +22,7 @@
 import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.util.List;
+import java.util.Queue;
 
 /**
  * Base class for rules that determine False touches.
@@ -30,13 +31,17 @@
     private final FalsingDataProvider mDataProvider;
 
     FalsingClassifier(FalsingDataProvider dataProvider) {
-        this.mDataProvider = dataProvider;
+        mDataProvider = dataProvider;
     }
 
     List<MotionEvent> getRecentMotionEvents() {
         return mDataProvider.getRecentMotionEvents();
     }
 
+    Queue<? extends List<MotionEvent>> getHistoricalEvents() {
+        return mDataProvider.getHistoricalMotionEvents();
+    }
+
     MotionEvent getFirstMotionEvent() {
         return mDataProvider.getFirstRecentMotionEvent();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
index 8d06748..4681f97 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
@@ -23,9 +23,13 @@
 
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Queue;
 
 import javax.inject.Inject;
 
@@ -35,18 +39,21 @@
 public class FalsingDataProvider {
 
     private static final long MOTION_EVENT_AGE_MS = 1000;
+    private static final long EXTENDED_MOTION_EVENT_AGE_MS = 30 * 1000;
     private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
 
     private final int mWidthPixels;
     private final int mHeightPixels;
     private final BatteryController mBatteryController;
+    private final SystemClock mSystemClock;
     private final float mXdpi;
     private final float mYdpi;
 
     private @Classifier.InteractionType int mInteractionType;
-    private final TimeLimitedMotionEventBuffer mRecentMotionEvents =
-            new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+    private final Deque<TimeLimitedMotionEventBuffer> mExtendedMotionEvents = new LinkedList<>();
 
+    private TimeLimitedMotionEventBuffer mRecentMotionEvents =
+            new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
     private boolean mDirty = true;
 
     private float mAngle = 0;
@@ -55,12 +62,14 @@
     private MotionEvent mLastMotionEvent;
 
     @Inject
-    public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController) {
+    public FalsingDataProvider(DisplayMetrics displayMetrics, BatteryController batteryController,
+            SystemClock systemClock) {
         mXdpi = displayMetrics.xdpi;
         mYdpi = displayMetrics.ydpi;
         mWidthPixels = displayMetrics.widthPixels;
         mHeightPixels = displayMetrics.heightPixels;
         mBatteryController = batteryController;
+        mSystemClock = systemClock;
 
         FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
         FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
@@ -81,7 +90,10 @@
         }
 
         if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mRecentMotionEvents.clear();
+            if (!mRecentMotionEvents.isEmpty()) {
+                mExtendedMotionEvents.addFirst(mRecentMotionEvents);
+                mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+            }
         }
         mRecentMotionEvents.addAll(motionEvents);
 
@@ -112,6 +124,16 @@
         return mRecentMotionEvents;
     }
 
+    /** Returns recent gestures, exclusive of the most recent gesture. Newer gestures come first. */
+    Queue<? extends List<MotionEvent>> getHistoricalMotionEvents() {
+        long nowMs = mSystemClock.uptimeMillis();
+
+        mExtendedMotionEvents.removeIf(
+                motionEvents -> motionEvents.isFullyExpired(nowMs - EXTENDED_MOTION_EVENT_AGE_MS));
+
+        return mExtendedMotionEvents;
+    }
+
     /**
      * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.java
new file mode 100644
index 0000000..47708f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/SingleTapClassifier.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.brightline;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+
+/**
+ * Falsing classifier that accepts or rejects a single gesture as a tap.
+ */
+public class SingleTapClassifier extends FalsingClassifier {
+    private final float mTouchSlop;
+    private String mReason;
+
+    SingleTapClassifier(FalsingDataProvider dataProvider, float touchSlop) {
+        super(dataProvider);
+        mTouchSlop = touchSlop;
+    }
+
+    @Override
+    boolean isFalseTouch() {
+        return !isTap(getRecentMotionEvents());
+    }
+
+    /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
+    public boolean isTap(List<MotionEvent> motionEvents) {
+        float downX = motionEvents.get(0).getX();
+        float downY = motionEvents.get(0).getY();
+
+        for (MotionEvent event : motionEvents) {
+            if (Math.abs(event.getX() - downX) >= mTouchSlop) {
+                mReason = "dX too big for a tap: "
+                        + Math.abs(event.getX() - downX)
+                        + "vs "
+                        + mTouchSlop;
+                return false;
+            } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
+                mReason = "dY too big for a tap: "
+                        + Math.abs(event.getY() - downY)
+                        + "vs "
+                        + mTouchSlop;
+                return false;
+            }
+        }
+        mReason = "";
+        return true;
+    }
+
+    @Override
+    String getReason() {
+        return mReason;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
index 9a83b5b..92aa7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBuffer.java
@@ -34,12 +34,24 @@
 public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
 
     private final LinkedList<MotionEvent> mMotionEvents;
-    private long mMaxAgeMs;
+    private final long mMaxAgeMs;
 
     TimeLimitedMotionEventBuffer(long maxAgeMs) {
         super();
-        this.mMaxAgeMs = maxAgeMs;
-        this.mMotionEvents = new LinkedList<>();
+        mMaxAgeMs = maxAgeMs;
+        mMotionEvents = new LinkedList<>();
+    }
+
+    /**
+     * Returns true if the most recent event in the buffer is past the expiration time.
+     *
+     * This method does not mutate the underlying data. This method does imply that, if the supplied
+     * expiration time is old enough and a new {@link MotionEvent} gets added to the buffer, all
+     * prior events would be removed.
+     */
+    public boolean isFullyExpired(long expirationMs) {
+        return mMotionEvents.isEmpty()
+                || mMotionEvents.getLast().getEventTime() <= expirationMs;
     }
 
     private void ejectOldEvents() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index d3d24be..96f2072 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -478,7 +478,7 @@
         val pendingIntent = PendingIntent.getActivity(context,
                 componentName.hashCode(),
                 intent,
-                0)
+                PendingIntent.FLAG_IMMUTABLE)
         val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
                 .setTitle(controlInfo.controlTitle)
                 .setSubtitle(controlInfo.controlSubtitle)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 58e49f8..6888d1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -134,7 +134,7 @@
         DOZING_UPDATE_SENSOR_TAP(441),
 
         @UiEvent(doc = "Dozing updated because on display auth was triggered from AOD.")
-        DOZING_UPDATE_AUTH_TRIGGERED(442);
+        DOZING_UPDATE_AUTH_TRIGGERED(657);
 
         private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 37bcb16..bd3b899 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -329,7 +329,7 @@
                     Method injectMethod = rootComponent.getClass()
                             .getMethod("inject", getClass());
                     injectMethod.invoke(rootComponent, this);
-                    Log.w("TAG", "mMediaManager is now: " + mMediaManager);
+                    Log.w(TAG, "mMediaManager is now: " + mMediaManager);
                 } catch (NoSuchMethodException ex) {
                     Log.e(TAG, "Failed to find inject method for KeyguardSliceProvider", ex);
                 } catch (IllegalAccessException | InvocationTargetException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4c68312..aea0dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -34,10 +34,12 @@
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricSourceType;
 import android.media.AudioAttributes;
@@ -88,6 +90,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardService;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
@@ -779,7 +782,7 @@
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
-        if (mContext.getResources().getBoolean(R.bool.config_enableKeyguardService)) {
+        if (isKeyguardServiceEnabled()) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
                             KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
@@ -957,6 +960,15 @@
         mUpdateMonitor.dispatchFinishedGoingToSleep(why);
     }
 
+    private boolean isKeyguardServiceEnabled() {
+        try {
+            return mContext.getPackageManager().getServiceInfo(
+                    new ComponentName(mContext, KeyguardService.class), 0).isEnabled();
+        } catch (NameNotFoundException e) {
+            return true;
+        }
+    }
+
     private long getLockTimeout(int userId) {
         // if the screen turned off because of timeout or the user hit the power button
         // and we don't need to lock immediately, set an alarm
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a27e9ac..8c66ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.power;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -334,10 +336,14 @@
     }
 
     private PendingIntent pendingBroadcast(String action) {
-        return PendingIntent.getBroadcastAsUser(mContext, 0,
-                new Intent(action).setPackage(mContext.getPackageName())
-                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                0, UserHandle.CURRENT);
+        return PendingIntent.getBroadcastAsUser(
+                mContext,
+                0 /* request code */,
+                new Intent(action)
+                        .setPackage(mContext.getPackageName())
+                        .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                FLAG_IMMUTABLE /* flags */,
+                UserHandle.CURRENT);
     }
 
     private static Intent settings(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index 6ac1e70..4f577f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -25,8 +25,7 @@
 import com.android.systemui.qs.TileLayout.exactly
 
 class DoubleLineTileLayout(
-    context: Context,
-    private val uiEventLogger: UiEventLogger
+    context: Context
 ) : ViewGroup(context), QSPanel.QSTileLayout {
 
     companion object {
@@ -84,7 +83,7 @@
         return false
     }
 
-    override fun setListening(listening: Boolean) {
+    override fun setListening(listening: Boolean, uiEventLogger: UiEventLogger) {
         if (_listening == listening) return
         _listening = listening
         for (record in mRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 8c7d459..addbd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -142,7 +142,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void setListening(boolean listening, UiEventLogger uiEventLogger) {
         if (mListening == listening) return;
         mListening = listening;
         updateListening();
@@ -150,7 +150,7 @@
 
     private void updateListening() {
         for (TilePage tilePage : mPages) {
-            tilePage.setListening(tilePage.getParent() == null ? false : mListening);
+            tilePage.setListening(tilePage.getParent() != null && mListening);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f4571d7..16e9590 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.qs;
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -62,7 +61,7 @@
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
     private View mDragHandle;
-    private View mQSPanelContainer;
+    private NonInterceptingScrollView mQSPanelContainer;
 
     private View mBackground;
 
@@ -127,18 +126,12 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
         // bottom and footer are inside the screen.
-        Configuration config = getResources().getConfiguration();
-        boolean navBelow = config.smallestScreenWidthDp >= 600
-                || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
         MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
 
         // The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to
         // subtract its height. We do not care if the collapsed notifications fit in the screen.
         int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
                 - getPaddingBottom();
-        if (navBelow) {
-            maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
-        }
 
         int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
                 + layoutParams.rightMargin;
@@ -184,7 +177,8 @@
         mBackground.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
     }
 
-    void updateResources(QSPanelController qsPanelController) {
+    void updateResources(QSPanelController qsPanelController,
+            QuickStatusBarHeaderController quickStatusBarHeaderController) {
         LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams();
         layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.quick_qs_offset_height);
@@ -196,7 +190,7 @@
         boolean marginsChanged = padding != mContentPadding;
         mContentPadding = padding;
         if (marginsChanged) {
-            updatePaddingsAndMargins(qsPanelController);
+            updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
         }
     }
 
@@ -217,12 +211,13 @@
 
     public void updateExpansion(boolean animate) {
         int height = calculateContainerHeight();
+        int scrollBottom = calculateContainerBottom();
         setBottom(getTop() + height);
-        mQSDetail.setBottom(getTop() + height);
+        mQSDetail.setBottom(getTop() + scrollBottom);
         // Pin the drag handle to the bottom of the panel.
-        mDragHandle.setTranslationY(height - mDragHandle.getHeight());
+        mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
         mBackground.setTop(mQSPanelContainer.getTop());
-        updateBackgroundBottom(height, animate);
+        updateBackgroundBottom(scrollBottom, animate);
     }
 
     private void updateBackgroundBottom(int height, boolean animated) {
@@ -246,13 +241,23 @@
                 + mHeader.getHeight();
     }
 
+    int calculateContainerBottom() {
+        int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
+                : Math.round(mQsExpansion
+                        * (heightOverride + mQSPanelContainer.getScrollRange()
+                                - mQSPanelContainer.getScrollY() - mHeader.getHeight()))
+                        + mHeader.getHeight();
+    }
+
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
         mDragHandle.setAlpha(1.0f - expansion);
         updateExpansion();
     }
 
-    private void updatePaddingsAndMargins(QSPanelController qsPanelController) {
+    private void updatePaddingsAndMargins(QSPanelController qsPanelController,
+            QuickStatusBarHeaderController quickStatusBarHeaderController) {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
             if (view == mQSCustomizer) {
@@ -271,7 +276,7 @@
             } else if (view == mHeader) {
                 // The header contains the QQS panel which needs to have special padding, to
                 // visually align them.
-                mHeader.setContentMargins(mContentPadding, mContentPadding);
+                quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
             } else {
                 view.setPaddingRelative(
                         mContentPadding,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 27d3221..3638395 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -35,7 +35,7 @@
             new ConfigurationController.ConfigurationListener() {
         @Override
         public void onConfigChanged(Configuration newConfig) {
-            mView.updateResources(mQsPanelController);
+            mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         }
     };
 
@@ -60,7 +60,7 @@
 
     @Override
     protected void onViewAttached() {
-        mView.updateResources(mQsPanelController);
+        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         mQsPanelController.setMediaVisibilityChangedListener((visible) -> {
             if (mQsPanelController.isShown()) {
                 mView.onMediaVisibilityChanged(true);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 043f5f1..dbdd04a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -300,7 +300,7 @@
                 ? View.VISIBLE
                 : View.INVISIBLE);
         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (mQsExpanded && !mStackScrollerOverscrolling));
+                || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
         mFooter.setVisibility(
                 !mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
                         || mShowCollapsedOnKeyguard)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 337f31e..87a8da0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 import static com.android.systemui.util.Utils.useQsMediaPlayer;
 
 import android.annotation.NonNull;
@@ -36,29 +34,20 @@
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.RemeasuringLinearLayout;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.animation.DisappearParameters;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /** View that represents the quick settings tile panel (when expanded/pulled down). **/
 public class QSPanel extends LinearLayout implements Tunable {
 
@@ -68,7 +57,6 @@
     private static final String TAG = "QSPanel";
 
     protected final Context mContext;
-    private final MediaHost mMediaHost;
 
     /**
      * The index where the content starts that needs to be moved between parents
@@ -81,7 +69,6 @@
     protected BrightnessSlider mToggleSliderController;
 
     private final H mHandler = new H();
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     /** Whether or not the QS media player feature is enabled. */
     protected boolean mUsingMediaPlayer;
     private int mVisualMarginStart;
@@ -91,8 +78,6 @@
     protected boolean mListening;
 
     private QSDetail.Callback mCallback;
-    private final QSLogger mQSLogger;
-    protected final UiEventLogger mUiEventLogger;
     protected QSTileHost mHost;
     private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
             new ArrayList<>();
@@ -108,7 +93,6 @@
     @Nullable
     private ViewGroup mHeaderContainer;
     private PageIndicator mFooterPageIndicator;
-    private boolean mGridContentVisible = true;
     private int mContentMarginStart;
     private int mContentMarginEnd;
     private int mVisualTilePadding;
@@ -129,23 +113,12 @@
     private int mFooterMarginStartHorizontal;
     private Consumer<Boolean> mMediaVisibilityChangedListener;
 
-
-    @Inject
-    public QSPanel(
-            @Named(VIEW_CONTEXT) Context context,
-            AttributeSet attrs,
-            QSLogger qsLogger,
-            @Named(QS_PANEL) MediaHost mediaHost,
-            UiEventLogger uiEventLogger
-    ) {
+    public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
         mUsingMediaPlayer = useQsMediaPlayer(context);
         mMediaTotalBottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.quick_settings_bottom_margin_media);
-        mMediaHost = mediaHost;
         mContext = context;
-        mQSLogger = qsLogger;
-        mUiEventLogger = uiEventLogger;
 
         setOrientation(VERTICAL);
 
@@ -175,7 +148,6 @@
             lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
             addView(mHorizontalLinearLayout, lp);
         }
-        mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), "");
     }
 
     protected void onMediaVisibilityChanged(Boolean visible) {
@@ -213,36 +185,6 @@
         return createRegularTileLayout();
     }
 
-    /**
-     * Update the way the media disappears based on if we're using the horizontal layout
-     */
-    void updateMediaDisappearParameters() {
-        if (!mUsingMediaPlayer) {
-            return;
-        }
-        DisappearParameters parameters = mMediaHost.getDisappearParameters();
-        if (mUsingHorizontalLayout) {
-            // Only height remaining
-            parameters.getDisappearSize().set(0.0f, 0.4f);
-            // Disappearing on the right side on the bottom
-            parameters.getGonePivot().set(1.0f, 1.0f);
-            // translating a bit horizontal
-            parameters.getContentTranslationFraction().set(0.25f, 1.0f);
-            parameters.setDisappearEnd(0.6f);
-        } else {
-            // Only width remaining
-            parameters.getDisappearSize().set(1.0f, 0.0f);
-            // Disappearing on the bottom
-            parameters.getGonePivot().set(0.0f, 1.0f);
-            // translating a bit vertical
-            parameters.getContentTranslationFraction().set(0.0f, 1.05f);
-            parameters.setDisappearEnd(0.95f);
-        }
-        parameters.setFadeStartPosition(0.95f);
-        parameters.setDisappearStart(0.0f);
-        mMediaHost.setDisappearParameters(parameters);
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mTileLayout instanceof PagedTileLayout) {
@@ -284,14 +226,6 @@
         setMeasuredDimension(getMeasuredWidth(), height);
     }
 
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mTileLayout != null) {
-            mTileLayout.setListening(false);
-        }
-        super.onDetachedFromWindow();
-    }
-
     protected String getDumpableTag() {
         return TAG;
     }
@@ -403,65 +337,6 @@
         mDivider = findViewById(R.id.divider);
     }
 
-    boolean switchTileLayout(boolean force, List<QSPanelControllerBase.TileRecord> records) {
-        /** Whether or not the QuickQSPanel currently contains a media player. */
-        boolean horizontal = shouldUseHorizontalLayout();
-        if (mDivider != null) {
-            if (!horizontal && mUsingMediaPlayer && mMediaHost.getVisible()) {
-                mDivider.setVisibility(View.VISIBLE);
-            } else {
-                mDivider.setVisibility(View.GONE);
-            }
-        }
-        if (horizontal != mUsingHorizontalLayout || force) {
-            mUsingHorizontalLayout = horizontal;
-            View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
-            View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
-            ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
-            QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout;
-            if (hiddenView != null &&
-                    (mRegularTileLayout != mHorizontalTileLayout ||
-                            hiddenView != mRegularTileLayout)) {
-                // Only hide the view if the horizontal and the regular view are different,
-                // otherwise its reattached.
-                hiddenView.setVisibility(View.GONE);
-            }
-            visibleView.setVisibility(View.VISIBLE);
-            switchAllContentToParent(newParent, newLayout);
-            reAttachMediaHost();
-            if (mTileLayout != null) {
-                mTileLayout.setListening(false);
-                for (QSPanelControllerBase.TileRecord record : records) {
-                    mTileLayout.removeTile(record);
-                    record.tile.removeCallback(record.callback);
-                }
-            }
-            mTileLayout = newLayout;
-            if (needsDynamicRowsAndColumns()) {
-                newLayout.setMinRows(horizontal ? 2 : 1);
-                // Let's use 3 columns to match the current layout
-                newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
-            }
-            updateTileLayoutMargins();
-            updateFooterMargin();
-            updateDividerMargin();
-            updateMediaDisappearParameters();
-            updateMediaHostContentMargins();
-            updateHorizontalLinearLayoutMargins();
-            updatePadding();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Sets the listening state of the current layout to the state of the view. Used after
-     * switching layouts.
-     */
-    public void reSetLayoutListening() {
-        mTileLayout.setListening(mListening);
-    }
-
     private void updateHorizontalLinearLayoutMargins() {
         if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
             LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
@@ -524,27 +399,19 @@
         }
     }
 
-    private boolean shouldUseHorizontalLayout() {
-        return mUsingMediaPlayer && mMediaHost.getVisible()
-                && getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
-    }
-
-    protected void reAttachMediaHost() {
+    /** Call when orientation has changed and MediaHost needs to be adjusted. */
+    private void reAttachMediaHost(ViewGroup hostView, boolean horizontal) {
         if (!mUsingMediaPlayer) {
             return;
         }
-        boolean horizontal = shouldUseHorizontalLayout();
-        ViewGroup host = mMediaHost.getHostView();
-
         ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
-        ViewGroup currentParent = (ViewGroup) host.getParent();
+        ViewGroup currentParent = (ViewGroup) hostView.getParent();
         if (currentParent != newParent) {
             if (currentParent != null) {
-                currentParent.removeView(host);
+                currentParent.removeView(hostView);
             }
-            newParent.addView(host);
-            LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
+            newParent.addView(hostView);
+            LinearLayout.LayoutParams layoutParams = (LayoutParams) hostView.getLayoutParams();
             layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
             layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
             layoutParams.weight = horizontal ? 1.2f : 0;
@@ -558,7 +425,6 @@
 
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
-        mQSLogger.logPanelExpanded(expanded, getDumpableTag());
         mExpanded = expanded;
         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
@@ -576,16 +442,10 @@
     }
 
     /** */
-    public void setListening(boolean listening, String cachedSpecs) {
-        if (mListening == listening) return;
+    public void setListening(boolean listening) {
         mListening = listening;
-        if (mTileLayout != null) {
-            mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), cachedSpecs);
-            mTileLayout.setListening(listening);
-        }
     }
 
-
     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
         int xInWindow = locationInWindow[0];
         int yInWindow = locationInWindow[1];
@@ -728,14 +588,6 @@
         fireScanStateChanged(scanState);
     }
 
-    void setGridContentVisibility(boolean visible) {
-        int newVis = visible ? VISIBLE : INVISIBLE;
-        setVisibility(newVis);
-        if (mGridContentVisible != visible) {
-            mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
-        }
-        mGridContentVisible = visible;
-    }
     private void fireShowingDetail(DetailAdapter detail, int x, int y) {
         if (mCallback != null) {
             mCallback.onShowingDetail(detail, x, y);
@@ -763,14 +615,15 @@
         return mDivider;
     }
 
-    public void setContentMargins(int startMargin, int endMargin) {
+    /** */
+    public void setContentMargins(int startMargin, int endMargin, ViewGroup mediaHostView) {
         // Only some views actually want this content padding, others want to go all the way
         // to the edge like the brightness slider
         mContentMarginStart = startMargin;
         mContentMarginEnd = endMargin;
         updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding,
                 mContentMarginEnd - mVisualTilePadding);
-        updateMediaHostContentMargins();
+        updateMediaHostContentMargins(mediaHostView);
         updateFooterMargin();
         updateDividerMargin();
     }
@@ -826,13 +679,13 @@
     /**
      * Update the margins of the media hosts
      */
-    protected void updateMediaHostContentMargins() {
+    protected void updateMediaHostContentMargins(ViewGroup mediaHostView) {
         if (mUsingMediaPlayer) {
             int marginStart = mContentMarginStart;
             if (mUsingHorizontalLayout) {
                 marginStart = 0;
             }
-            updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd);
+            updateMargins(mediaHostView, marginStart, mContentMarginEnd);
         }
     }
 
@@ -871,6 +724,45 @@
         mSecurityFooter = view;
     }
 
+    void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force,
+            UiEventLogger uiEventLogger) {
+        if (horizontal != mUsingHorizontalLayout || force) {
+            mUsingHorizontalLayout = horizontal;
+            View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
+            View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
+            ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
+            QSPanel.QSTileLayout newLayout = horizontal
+                    ? mHorizontalTileLayout : mRegularTileLayout;
+            if (hiddenView != null
+                    && (mRegularTileLayout != mHorizontalTileLayout
+                    || hiddenView != mRegularTileLayout)) {
+                // Only hide the view if the horizontal and the regular view are different,
+                // otherwise its reattached.
+                hiddenView.setVisibility(View.GONE);
+            }
+            visibleView.setVisibility(View.VISIBLE);
+            switchAllContentToParent(newParent, newLayout);
+            reAttachMediaHost(mediaHostView, horizontal);
+            mTileLayout = newLayout;
+            newLayout.setListening(mListening, uiEventLogger);
+            if (needsDynamicRowsAndColumns()) {
+                newLayout.setMinRows(horizontal ? 2 : 1);
+                // Let's use 3 columns to match the current layout
+                newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS);
+            }
+            updateMargins(mediaHostView);
+        }
+    }
+
+    private void updateMargins(ViewGroup mediaHostView) {
+        updateTileLayoutMargins();
+        updateFooterMargin();
+        updateDividerMargin();
+        updateMediaHostContentMargins(mediaHostView);
+        updateHorizontalLinearLayoutMargins();
+        updatePadding();
+    }
+
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
@@ -912,7 +804,7 @@
         boolean updateResources();
 
         /** */
-        void setListening(boolean listening);
+        void setListening(boolean listening, UiEventLogger uiEventLogger);
 
         /**
          * Set the minimum number of rows to show
@@ -924,7 +816,7 @@
         }
 
         /**
-         * Set the max number of collums to show
+         * Set the max number of columns to show
          *
          * @param maxColumns the maximum
          *
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index eaa3ed0..8ee284b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import android.annotation.NonNull;
 import android.content.res.Configuration;
@@ -27,6 +28,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
@@ -34,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -57,6 +60,9 @@
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
     private final BrightnessSlider mBrightnessSlider;
 
+    private BrightnessMirrorController mBrightnessMirrorController;
+    private boolean mGridContentVisible = true;
+
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             new QSPanel.OnConfigurationChangedListener() {
         @Override
@@ -69,7 +75,6 @@
             updateBrightnessMirror();
         }
     };
-    private BrightnessMirrorController mBrightnessMirrorController;
 
     private final BrightnessMirrorController.BrightnessMirrorListener mBrightnessMirrorListener =
             mirror -> updateBrightnessMirror();
@@ -77,13 +82,14 @@
     @Inject
     QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
             QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
             QSTileRevealController.Factory qsTileRevealControllerFactory,
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
-            BrightnessController.Factory brightnessControllerFactory,
+            QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSlider.Factory brightnessSliderFactory) {
-        super(view, qstileHost, qsCustomizerController, mediaHost, metricsLogger, uiEventLogger,
-                dumpManager);
+        super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
+                metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
@@ -111,7 +117,7 @@
     protected void onViewAttached() {
         super.onViewAttached();
 
-        mView.updateMediaDisappearParameters();
+        updateMediaDisappearParameters();
 
         mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
         mView.updateResources();
@@ -142,11 +148,6 @@
         super.onViewDetached();
     }
 
-    /** TODO(b/168904199): Remove this method once view is controllerized. */
-    QSPanel getView() {
-        return mView;
-    }
-
     /**
      * Set the header container of quick settings.
      */
@@ -246,7 +247,12 @@
 
     /** */
     public void setGridContentVisibility(boolean visible) {
-        mView.setGridContentVisibility(visible);
+        int newVis = visible ? View.VISIBLE : View.INVISIBLE;
+        setVisibility(newVis);
+        if (mGridContentVisible != visible) {
+            mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
+        }
+        mGridContentVisible = visible;
     }
 
     public boolean isLayoutRtl() {
@@ -277,7 +283,7 @@
 
     /** */
     public void setContentMargins(int startMargin, int endMargin) {
-        mView.setContentMargins(startMargin, endMargin);
+        mView.setContentMargins(startMargin, endMargin, mMediaHost.getHostView());
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2c4c8c2..4418a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -17,10 +17,12 @@
 package com.android.systemui.qs;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.metrics.LogMaker;
+import android.view.View;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
@@ -31,7 +33,9 @@
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.animation.DisappearParameters;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -39,6 +43,8 @@
 import java.util.Collection;
 import java.util.stream.Collectors;
 
+import javax.inject.Named;
+
 import kotlin.Unit;
 import kotlin.jvm.functions.Function1;
 
@@ -51,9 +57,11 @@
         implements Dumpable{
     protected final QSTileHost mHost;
     private final QSCustomizerController mQsCustomizerController;
+    private final boolean mUsingMediaPlayer;
     protected final MediaHost mMediaHost;
-    private final MetricsLogger mMetricsLogger;
+    protected final MetricsLogger mMetricsLogger;
     private final UiEventLogger mUiEventLogger;
+    private final QSLogger mQSLogger;
     private final DumpManager mDumpManager;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
 
@@ -81,19 +89,30 @@
         return null;
     };
 
+    private boolean mUsingHorizontalLayout;
+
     protected QSPanelControllerBase(T view, QSTileHost host,
-            QSCustomizerController qsCustomizerController, MediaHost mediaHost,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
+            QSCustomizerController qsCustomizerController,
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
+            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
+            DumpManager dumpManager) {
         super(view);
         mHost = host;
         mQsCustomizerController = qsCustomizerController;
+        mUsingMediaPlayer = usingMediaPlayer;
         mMediaHost = mediaHost;
         mMetricsLogger = metricsLogger;
         mUiEventLogger = uiEventLogger;
+        mQSLogger = qsLogger;
         mDumpManager = dumpManager;
     }
 
     @Override
+    protected void onInit() {
+        mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
+    }
+
+    @Override
     protected void onViewAttached() {
         mQsTileRevealController = createTileRevealController();
         if (mQsTileRevealController != null) {
@@ -115,6 +134,8 @@
         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
         mHost.removeCallback(mQSHostCallback);
 
+        mView.getTileLayout().setListening(false, mUiEventLogger);
+
         mMediaHost.removeVisibilityChangeListener(mMediaHostVisibilityListener);
 
         for (TileRecord record : mRecords) {
@@ -205,6 +226,11 @@
 
     /** */
     public void setExpanded(boolean expanded) {
+        if (mView.isExpanded() == expanded) {
+            return;
+        }
+        mQSLogger.logPanelExpanded(expanded, mView.getDumpableTag());
+
         mView.setExpanded(expanded);
         mMetricsLogger.visibility(MetricsEvent.QS_PANEL, expanded);
         if (!expanded) {
@@ -238,17 +264,77 @@
 
 
     void setListening(boolean listening) {
-        mView.setListening(listening, mCachedSpecs);
+        mView.setListening(listening);
+
+        if (mView.getTileLayout() != null) {
+            mQSLogger.logAllTilesChangeListening(listening, mView.getDumpableTag(), mCachedSpecs);
+            mView.getTileLayout().setListening(listening, mUiEventLogger);
+        }
     }
 
     boolean switchTileLayout(boolean force) {
-        if (mView.switchTileLayout(force, mRecords)) {
+        /** Whether or not the QuickQSPanel currently contains a media player. */
+        boolean horizontal = shouldUseHorizontalLayout();
+        if (mView.getDivider() != null) {
+            if (!horizontal && mUsingMediaPlayer && mMediaHost.getVisible()) {
+                mView.getDivider().setVisibility(View.VISIBLE);
+            } else {
+                mView.getDivider().setVisibility(View.GONE);
+            }
+        }
+        if (horizontal != mUsingHorizontalLayout || force) {
+            mUsingHorizontalLayout = horizontal;
+            for (QSPanelControllerBase.TileRecord record : mRecords) {
+                mView.removeTile(record);
+                record.tile.removeCallback(record.callback);
+            }
+            mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force,
+                    mUiEventLogger);
+            updateMediaDisappearParameters();
+
             setTiles();
+
             return true;
         }
         return false;
     }
 
+    /**
+     * Update the way the media disappears based on if we're using the horizontal layout
+     */
+    void updateMediaDisappearParameters() {
+        if (!mUsingMediaPlayer) {
+            return;
+        }
+        DisappearParameters parameters = mMediaHost.getDisappearParameters();
+        if (mUsingHorizontalLayout) {
+            // Only height remaining
+            parameters.getDisappearSize().set(0.0f, 0.4f);
+            // Disappearing on the right side on the bottom
+            parameters.getGonePivot().set(1.0f, 1.0f);
+            // translating a bit horizontal
+            parameters.getContentTranslationFraction().set(0.25f, 1.0f);
+            parameters.setDisappearEnd(0.6f);
+        } else {
+            // Only width remaining
+            parameters.getDisappearSize().set(1.0f, 0.0f);
+            // Disappearing on the bottom
+            parameters.getGonePivot().set(0.0f, 1.0f);
+            // translating a bit vertical
+            parameters.getContentTranslationFraction().set(0.0f, 1.05f);
+            parameters.setDisappearEnd(0.95f);
+        }
+        parameters.setFadeStartPosition(0.95f);
+        parameters.setDisappearStart(0.0f);
+        mMediaHost.setDisappearParameters(parameters);
+    }
+
+    boolean shouldUseHorizontalLayout() {
+        return mUsingMediaPlayer && mMediaHost.getVisible()
+                && getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
     private void logTiles() {
         for (int i = 0; i < mRecords.size(); i++) {
             QSTile tile = mRecords.get(i).tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 84c2ac24..c89f8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -29,14 +26,9 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
-import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.logging.QSLogger;
-
-import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * Version of QSPanel that only shows N Quick Tiles in the QS Header.
@@ -51,15 +43,8 @@
     private boolean mDisabledByPolicy;
     private int mMaxTiles;
 
-
-    @Inject
-    public QuickQSPanel(
-            @Named(VIEW_CONTEXT) Context context,
-            AttributeSet attrs,
-            QSLogger qsLogger,
-            @Named(QUICK_QS_PANEL) MediaHost mediaHost,
-            UiEventLogger uiEventLogger) {
-        super(context, attrs, qsLogger, mediaHost, uiEventLogger);
+    public QuickQSPanel(Context context, AttributeSet attrs) {
+        super(context, attrs);
         mMaxTiles = Math.min(DEFAULT_MAX_TILES,
                 getResources().getInteger(R.integer.quick_qs_panel_max_columns));
         applyBottomMargin((View) mRegularTileLayout);
@@ -79,12 +64,12 @@
 
     @Override
     public TileLayout createRegularTileLayout() {
-        return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger);
+        return new QuickQSPanel.HeaderTileLayout(mContext);
     }
 
     @Override
     protected QSTileLayout createHorizontalTileLayout() {
-        return new DoubleLineTileLayout(mContext, mUiEventLogger);
+        return new DoubleLineTileLayout(mContext);
     }
 
     @Override
@@ -198,13 +183,10 @@
 
     private static class HeaderTileLayout extends TileLayout {
 
-        private final UiEventLogger mUiEventLogger;
-
         private Rect mClippingBounds = new Rect();
 
-        public HeaderTileLayout(Context context, UiEventLogger uiEventLogger) {
+        HeaderTileLayout(Context context) {
             super(context);
-            mUiEventLogger = uiEventLogger;
             setClipChildren(false);
             setClipToPadding(false);
             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
@@ -332,14 +314,14 @@
         }
 
         @Override
-        public void setListening(boolean listening) {
+        public void setListening(boolean listening, UiEventLogger uiEventLogger) {
             boolean startedListening = !mListening && listening;
-            super.setListening(listening);
+            super.setListening(listening, uiEventLogger);
             if (startedListening) {
                 // getNumVisibleTiles() <= mRecords.size()
                 for (int i = 0; i < getNumVisibleTiles(); i++) {
                     QSTile tile = mRecords.get(i).tile;
-                    mUiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
+                    uiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
                             tile.getMetricsSpec(), tile.getInstanceId());
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 174a81f..cca0e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
@@ -27,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.logging.QSLogger;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -38,8 +40,6 @@
 @QSScope
 public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
 
-    private List<QSTile> mAllTiles = new ArrayList<>();
-
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             newConfig -> {
                 int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
@@ -51,11 +51,12 @@
     @Inject
     QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
             QSCustomizerController qsCustomizerController,
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
             DumpManager dumpManager) {
-        super(view, qsTileHost, qsCustomizerController, mediaHost, metricsLogger, uiEventLogger,
-                dumpManager);
+        super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
+                uiEventLogger, qsLogger, dumpManager);
     }
 
     @Override
@@ -89,14 +90,19 @@
 
     @Override
     public void setTiles() {
-        mAllTiles.clear();
+        List<QSTile> tiles = new ArrayList();
         for (QSTile tile : mHost.getTiles()) {
-            mAllTiles.add(tile);
-            if (mAllTiles.size() == QuickQSPanel.DEFAULT_MAX_TILES) {
+            tiles.add(tile);
+            if (tiles.size() == mView.getNumQuickTiles()) {
                 break;
             }
         }
-        super.setTiles(mAllTiles.subList(0, mView.getNumQuickTiles()), true);
+        super.setTiles(tiles, true);
+    }
+
+    /** */
+    public void setContentMargins(int marginStart, int marginEnd) {
+        mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
     }
 
     public int getNumQuickTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 09894e5..b85ad81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -51,8 +51,6 @@
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.privacy.OngoingPrivacyChip;
 import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -313,10 +311,11 @@
                 .build();
     }
 
-    public void setExpanded(boolean expanded) {
+    /** */
+    public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
-        mHeaderQsPanel.setExpanded(expanded);
+        quickQSPanelController.setExpanded(expanded);
         updateEverything();
     }
 
@@ -466,14 +465,15 @@
     }
 
     /** */
-    public void setContentMargins(int marginStart, int marginEnd) {
+    public void setContentMargins(int marginStart, int marginEnd,
+            QuickQSPanelController quickQSPanelController) {
         mContentMarginStart = marginStart;
         mContentMarginEnd = marginEnd;
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
             if (view == mHeaderQsPanel) {
                 // QS panel doesn't lays out some of its content full width
-                mHeaderQsPanel.setContentMargins(marginStart, marginEnd);
+                quickQSPanelController.setContentMargins(marginStart, marginEnd);
             } else {
                 MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
                 lp.setMarginStart(marginStart);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 5ee9df4..c5b76fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -379,6 +379,11 @@
                 mZenModeController.getConsolidatedPolicy());
     }
 
+    public void setContentMargins(int contentPaddingStart, int contentPaddingEnd) {
+        mView.setContentMargins(contentPaddingStart, contentPaddingEnd, mHeaderQsPanelController);
+    }
+
+
     private static class ClockDemoModeReceiver implements DemoMode {
         private Clock mClockView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 4ab7afd..348dca5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -9,6 +9,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
@@ -59,8 +60,12 @@
         return getTop();
     }
 
-    @Override
     public void setListening(boolean listening) {
+        setListening(listening, null);
+    }
+
+    @Override
+    public void setListening(boolean listening, UiEventLogger uiEventLogger) {
         if (mListening == listening) return;
         mListening = listening;
         for (TileRecord record : mRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index f3bf306..2363aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.qs.dagger;
 
+import static com.android.systemui.util.Utils.useQsMediaPlayer;
+
+import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -44,6 +47,7 @@
 @Module
 public interface QSFragmentModule {
     String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
+    String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
 
     /** */
     @Provides
@@ -108,4 +112,12 @@
     static View providesQSSecurityFooterView(LayoutInflater layoutInflater, QSPanel qsPanel) {
         return layoutInflater.inflate(R.layout.quick_settings_footer, qsPanel, false);
     }
+
+    /** */
+    @Provides
+    @Named(QS_USING_MEDIA_PLAYER)
+    static boolean providesQSUsingMediaPlayer(Context context) {
+        return useQsMediaPlayer(context);
+    }
+
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 216b83f..84818ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -41,6 +41,7 @@
 public class NotificationHeaderUtil {
 
     private static final TextViewComparator sTextViewComparator = new TextViewComparator();
+    private static final TextViewComparator sAppNameComparator = new AppNameComparator();
     private static final VisibilityApplicator sVisibilityApplicator = new VisibilityApplicator();
     private static final VisibilityApplicator sAppNameApplicator = new AppNameApplicator();
     private static  final DataExtractor sIconExtractor = new DataExtractor() {
@@ -119,7 +120,7 @@
                 mRow,
                 com.android.internal.R.id.app_name_text,
                 null,
-                sTextViewComparator,
+                sAppNameComparator,
                 sAppNameApplicator));
         mComparators.add(HeaderProcessor.forTextView(mRow,
                 com.android.internal.R.id.header_text));
@@ -389,4 +390,17 @@
             super.apply(parent, view, apply, reset);
         }
     }
+
+    private static class AppNameComparator extends TextViewComparator {
+        @Override
+        public boolean compare(View parent, View child, Object parentData, Object childData) {
+            if (isEmpty(child)) {
+                // In headerless notifications the AppName view exists but is usually GONE (and not
+                // populated).  We need to treat this case as equal to the header in order to
+                // deduplicate the view.
+                return true;
+            }
+            return super.compare(parent, child, parentData, childData);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 41ce51c..0b79387 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -23,32 +23,44 @@
 
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.DoubleTapHelper;
+import com.android.systemui.statusbar.phone.NotificationTapHelper;
+import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
 
 /**
  * Controller for {@link ActivatableNotificationView}
  */
-public class ActivatableNotificationViewController {
-    private final ActivatableNotificationView mView;
+public class ActivatableNotificationViewController
+        extends ViewController<ActivatableNotificationView> {
     private final ExpandableOutlineViewController mExpandableOutlineViewController;
     private final AccessibilityManager mAccessibilityManager;
     private final FalsingManager mFalsingManager;
-    private DoubleTapHelper mDoubleTapHelper;
-    private boolean mNeedsDimming;
+    private final NotificationTapHelper mNotificationTapHelper;
+    private final TouchHandler mTouchHandler = new TouchHandler();
 
-    private TouchHandler mTouchHandler = new TouchHandler();
+    private boolean mNeedsDimming;
 
     @Inject
     public ActivatableNotificationViewController(ActivatableNotificationView view,
+            NotificationTapHelper.Factory notificationTapHelpFactory,
             ExpandableOutlineViewController expandableOutlineViewController,
             AccessibilityManager accessibilityManager, FalsingManager falsingManager) {
-        mView = view;
+        super(view);
         mExpandableOutlineViewController = expandableOutlineViewController;
         mAccessibilityManager = accessibilityManager;
         mFalsingManager = falsingManager;
 
+        mNotificationTapHelper = notificationTapHelpFactory.create(
+                (active) -> {
+                    if (active) {
+                        mView.makeActive();
+                        mFalsingManager.onNotificationActive();
+                    } else {
+                        mView.makeInactive(true /* animate */);
+                    }
+                }, mView::performClick, mView::handleSlideBack);
+
         mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() {
             @Override
             public void onActivated(ActivatableNotificationView view) {
@@ -64,25 +76,25 @@
     /**
      * Initialize the controller, setting up handlers and other behavior.
      */
-    public void init() {
+    @Override
+    public void onInit() {
         mExpandableOutlineViewController.init();
-        mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> {
-            if (active) {
-                mView.makeActive();
-                mFalsingManager.onNotificationActive();
-            } else {
-                mView.makeInactive(true /* animate */);
-            }
-        }, mView::performClick, mView::handleSlideBack,
-                mFalsingManager::onNotificationDoubleTap);
         mView.setOnTouchListener(mTouchHandler);
         mView.setTouchHandler(mTouchHandler);
-        mView.setOnDimmedListener(dimmed -> {
-            mNeedsDimming = dimmed;
-        });
+        mView.setOnDimmedListener(dimmed -> mNeedsDimming = dimmed);
         mView.setAccessibilityManager(mAccessibilityManager);
     }
 
+    @Override
+    protected void onViewAttached() {
+
+    }
+
+    @Override
+    protected void onViewDetached() {
+
+    }
+
     class TouchHandler implements Gefingerpoken, View.OnTouchListener {
         private boolean mBlockNextTouch;
 
@@ -103,7 +115,7 @@
                     // let's ensure we have a ripple
                     return false;
                 }
-                result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight());
+                result = mNotificationTapHelper.onTouchEvent(ev, mView.getActualHeight());
             } else {
                 return false;
             }
@@ -117,7 +129,7 @@
                     && !mAccessibilityManager.isTouchExplorationEnabled()) {
                 if (!mView.isActive()) {
                     return true;
-                } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+                } else if (mFalsingManager.isFalseDoubleTap()) {
                     mBlockNextTouch = true;
                     mView.makeInactive(true /* animate */);
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 3287745..74e6c00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1369,16 +1369,8 @@
             bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
             bubbleButton.setVisibility(VISIBLE);
             actionContainer.setVisibility(VISIBLE);
-
-            int paddingEnd = getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.bubble_visible_padding_end);
-            actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
         } else  {
             bubbleButton.setVisibility(GONE);
-
-            int paddingEnd = getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.bubble_gone_padding_end);
-            actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 447fa43..1034b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -295,7 +295,7 @@
             mMenuContainer = new FrameLayout(mContext);
         }
         final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0) == 1;
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1;
         if (newFlowHideShelf) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index d228ce1..05db67d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -56,6 +56,7 @@
 
     private CachingIconView mIcon;
     private NotificationExpandButton mExpandButton;
+    private View mAltExpandTarget;
     protected NotificationHeaderView mNotificationHeader;
     protected NotificationTopLineView mNotificationTopLine;
     private TextView mHeaderText;
@@ -106,6 +107,7 @@
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
         mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
+        mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
         mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
@@ -260,6 +262,9 @@
     public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
         mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
         mExpandButton.setOnClickListener(expandable ? onClickListener : null);
+        if (mAltExpandTarget != null) {
+            mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
+        }
         if (mNotificationHeader != null) {
             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 8050fea..885048d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -41,7 +41,6 @@
     private static final float MAX_PULSE_HEIGHT = 100000f;
 
     private final SectionProvider mSectionProvider;
-    private ArrayList<View> mDraggedViews = new ArrayList<>();
     private int mScrollY;
     private int mAnchorViewIndex;
     private int mAnchorViewY;
@@ -161,19 +160,6 @@
         mAnchorViewY = anchorViewY;
     }
 
-    /** Call when dragging begins. */
-    public void onBeginDrag(View view) {
-        mDraggedViews.add(view);
-    }
-
-    public void onDragFinished(View view) {
-        mDraggedViews.remove(view);
-    }
-
-    public ArrayList<View> getDraggedViews() {
-        return mDraggedViews;
-    }
-
     /**
      * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
      *               translucent and everything is scaled back a bit.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index fbcfef3..742e517 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -353,31 +353,24 @@
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
     private ViewTreeObserver.OnPreDrawListener mShadowUpdater
-            = new ViewTreeObserver.OnPreDrawListener() {
-
-        @Override
-        public boolean onPreDraw() {
-            updateViewShadows();
-            return true;
-        }
-    };
+            = () -> {
+                updateViewShadows();
+                return true;
+            };
     private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
                 updateBackground();
                 return true;
             };
-    private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
-        @Override
-        public int compare(ExpandableView view, ExpandableView otherView) {
-            float endY = view.getTranslationY() + view.getActualHeight();
-            float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
-            if (endY < otherEndY) {
-                return -1;
-            } else if (endY > otherEndY) {
-                return 1;
-            } else {
-                // The two notifications end at the same location
-                return 0;
-            }
+    private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
+        float endY = view.getTranslationY() + view.getActualHeight();
+        float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
+        if (endY < otherEndY) {
+            return -1;
+        } else if (endY > otherEndY) {
+            return 1;
+        } else {
+            // The two notifications end at the same location
+            return 0;
         }
     };
     private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
@@ -415,8 +408,6 @@
      */
     private float mBackgroundXFactor = 1f;
 
-    private boolean mSwipingInProgress;
-
     private boolean mUsingLightTheme;
     private boolean mQsExpanded;
     private boolean mForwardScrollable;
@@ -834,9 +825,9 @@
         if (!mShouldDrawNotificationBackground) {
             return;
         }
-        final boolean clearUndershelf = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0 /* show background by default */) == 1;
-        if (clearUndershelf) {
+        final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1;
+        if (newFlowHideShelf) {
             mBackgroundPaint.setColor(Color.TRANSPARENT);
             invalidate();
             return;
@@ -2554,12 +2545,13 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = (ExpandableView) getChildAt(i);
-            if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
-                    && child != mShelf) {
+            if (child.getVisibility() != View.GONE
+                    && !(child instanceof StackScrollerDecorView)
+                    && child != mShelf
+                    && mSwipeHelper.getSwipedView() != child) {
                 children.add(child);
             }
         }
-
         return children;
     }
 
@@ -3577,7 +3569,10 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onGenericMotionEvent(MotionEvent event) {
-        if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
+        if (!isScrollingEnabled()
+                || !mIsExpanded
+                || mSwipeHelper.isSwiping()
+                || mExpandingNotification
                 || mDisallowScrollingInThisMotion) {
             return false;
         }
@@ -4077,14 +4072,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    void setSwipingInProgress(boolean swiping) {
-        mSwipingInProgress = swiping;
-        if (swiping) {
-            requestDisallowInterceptTouchEvent(true);
-        }
-    }
-
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onWindowFocusChanged(boolean hasWindowFocus) {
@@ -4127,9 +4114,8 @@
             mStatusBar.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
-            ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
-            if (draggedViews.size() > 0) {
-                draggedViews.clear();
+            if (mSwipeHelper.isSwiping()) {
+                mSwipeHelper.resetSwipeState();
                 updateContinuousShadowDrawing();
             }
         }
@@ -5196,15 +5182,11 @@
             ExpandableView child = (ExpandableView) getTransientView(i);
             child.dump(fd, pw, args);
         }
-        ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
-        int draggedCount = draggedViews.size();
-        pw.println("  Dragged Views: " + draggedCount);
-        for (int i = 0; i < draggedCount; i++) {
-            View view = draggedViews.get(i);
-            if (view instanceof ExpandableView) {
-                ExpandableView expandableView = (ExpandableView) view;
-                expandableView.dump(fd, pw, args);
-            }
+        View swipedView = mSwipeHelper.getSwipedView();
+        pw.println("  Swiped view: " + swipedView);
+        if (swipedView instanceof ExpandableView) {
+            ExpandableView expandableView = (ExpandableView) swipedView;
+            expandableView.dump(fd, pw, args);
         }
     }
 
@@ -5522,12 +5504,16 @@
         mSwipedOutViews.add(v);
     }
 
-    void addDraggedView(View view) {
-        mAmbientState.onBeginDrag(view);
+    void onSwipeBegin() {
+        requestDisallowInterceptTouchEvent(true);
+        updateFirstAndLastBackgroundViews();
+        updateContinuousShadowDrawing();
+        updateContinuousBackgroundDrawing();
+        requestChildrenUpdate();
     }
 
-    void removeDraggedView(View view) {
-        mAmbientState.onDragFinished(view);
+    void onSwipeEnd() {
+        updateFirstAndLastBackgroundViews();
     }
 
     void setTopHeadsUpEntry(NotificationEntry topEntry) {
@@ -5539,10 +5525,6 @@
         mAmbientState.setHasAlertEntries(numHeadsUp > 0);
     }
 
-    boolean getSwipingInProgress() {
-        return mSwipingInProgress;
-    }
-
     public boolean getIsExpanded() {
         return mIsExpanded;
     }
@@ -5583,10 +5565,6 @@
         mTouchHandler = touchHandler;
     }
 
-    boolean isSwipingInProgress() {
-        return mSwipingInProgress;
-    }
-
     boolean getCheckSnoozeLeaveBehind() {
         return mCheckForLeavebehind;
     }
@@ -5661,9 +5639,17 @@
         mSectionsManager.updateSectionBoundaries(reason);
     }
 
+    boolean isSilkDismissEnabled() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* enabled by default */) == 1;
+    }
+
     void updateContinuousBackgroundDrawing() {
+        if (isSilkDismissEnabled()) {
+            return;
+        }
         boolean continuousBackground = !mAmbientState.isFullyAwake()
-                && !mAmbientState.getDraggedViews().isEmpty();
+                && mSwipeHelper.isSwiping();
         if (continuousBackground != mContinuousBackgroundUpdate) {
             mContinuousBackgroundUpdate = continuousBackground;
             if (continuousBackground) {
@@ -5677,7 +5663,7 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void updateContinuousShadowDrawing() {
         boolean continuousShadowUpdate = mAnimationRunning
-                || !mAmbientState.getDraggedViews().isEmpty();
+                || mSwipeHelper.isSwiping();
         if (continuousShadowUpdate != mContinuousShadowUpdate) {
             if (continuousShadowUpdate) {
                 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7cee365..006d8da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -361,7 +361,6 @@
 
                 @Override
                 public void onDragCancelled(View v) {
-                    mView.setSwipingInProgress(false);
                     mFalsingManager.onNotificationStopDismissing();
                 }
 
@@ -392,14 +391,10 @@
                  */
 
                 public void handleChildViewDismissed(View view) {
-                    mView.setSwipingInProgress(false);
                     if (mView.getDismissAllInProgress()) {
                         return;
                     }
-
-                    mView.removeDraggedView(view);
-                    mView.updateContinuousShadowDrawing();
-
+                    mView.onSwipeEnd();
                     if (view instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) view;
                         if (row.isHeadsUp()) {
@@ -454,18 +449,12 @@
                 @Override
                 public void onBeginDrag(View v) {
                     mFalsingManager.onNotificationStartDismissing();
-                    mView.setSwipingInProgress(true);
-                    mView.addDraggedView(v);
-                    mView.updateContinuousShadowDrawing();
-                    mView.updateContinuousBackgroundDrawing();
-                    mView.requestChildrenUpdate();
+                    mView.onSwipeBegin();
                 }
 
                 @Override
                 public void onChildSnappedBack(View animView, float targetLeft) {
-                    mView.addDraggedView(animView);
-                    mView.updateContinuousShadowDrawing();
-                    mView.updateContinuousBackgroundDrawing();
+                    mView.onSwipeEnd();
                     if (animView instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
                         if (row.isPinned() && !canChildBeDismissed(row)
@@ -1536,12 +1525,12 @@
 
             NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
             boolean expandWantsIt = false;
-            boolean swipingInProgress = mView.isSwipingInProgress();
-            if (!swipingInProgress && !mView.getOnlyScrollingInThisMotion() && guts == null) {
+            if (!mSwipeHelper.isSwiping()
+                    && !mView.getOnlyScrollingInThisMotion() && guts == null) {
                 expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
             }
             boolean scrollWantsIt = false;
-            if (!swipingInProgress && !mView.isExpandingNotification()) {
+            if (!mSwipeHelper.isSwiping() && !mView.isExpandingNotification()) {
                 scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
             }
             boolean swipeWantsIt = false;
@@ -1582,10 +1571,9 @@
                     || ev.getActionMasked() == MotionEvent.ACTION_UP;
             mView.handleEmptySpaceClick(ev);
             boolean expandWantsIt = false;
-            boolean swipingInProgress = mView.getSwipingInProgress();
             boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
             boolean expandingNotification = mView.isExpandingNotification();
-            if (mView.getIsExpanded() && !swipingInProgress && !onlyScrollingInThisMotion
+            if (mView.getIsExpanded() && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion
                     && guts == null) {
                 ExpandHelper expandHelper = mView.getExpandHelper();
                 if (isCancelOrUp) {
@@ -1600,7 +1588,7 @@
                 }
             }
             boolean scrollerWantsIt = false;
-            if (mView.isExpanded() && !swipingInProgress && !expandingNotification
+            if (mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification
                     && !mView.getDisallowScrollingInThisMotion()) {
                 scrollerWantsIt = mView.onScrollTouch(ev);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
deleted file mode 100644
index 78ea5c0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.R;
-
-/**
- * Detects a double tap.
- */
-public class DoubleTapHelper {
-
-    private static final long DOUBLETAP_TIMEOUT_MS = 1200;
-
-    private final View mView;
-    private final ActivationListener mActivationListener;
-    private final DoubleTapListener mDoubleTapListener;
-    private final SlideBackListener mSlideBackListener;
-    private final DoubleTapLogListener mDoubleTapLogListener;
-
-    private float mTouchSlop;
-    private float mDoubleTapSlop;
-
-    private boolean mActivated;
-
-    private float mDownX;
-    private float mDownY;
-    private boolean mTrackTouch;
-
-    private float mActivationX;
-    private float mActivationY;
-    private Runnable mTapTimeoutRunnable = this::makeInactive;
-
-    public DoubleTapHelper(View view, ActivationListener activationListener,
-            DoubleTapListener doubleTapListener, SlideBackListener slideBackListener,
-            DoubleTapLogListener doubleTapLogListener) {
-        mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
-        mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop);
-        mView = view;
-
-        mActivationListener = activationListener;
-        mDoubleTapListener = doubleTapListener;
-        mSlideBackListener = slideBackListener;
-        mDoubleTapLogListener = doubleTapLogListener;
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        return onTouchEvent(event, Integer.MAX_VALUE);
-    }
-
-    public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
-        int action = event.getActionMasked();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                mDownX = event.getX();
-                mDownY = event.getY();
-                mTrackTouch = true;
-                if (mDownY > maxTouchableHeight) {
-                    mTrackTouch = false;
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!isWithinTouchSlop(event)) {
-                    makeInactive();
-                    mTrackTouch = false;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (isWithinTouchSlop(event)) {
-                    if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
-                        return true;
-                    }
-                    if (!mActivated) {
-                        makeActive();
-                        mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
-                        mActivationX = event.getX();
-                        mActivationY = event.getY();
-                    } else {
-                        boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event);
-                        if (mDoubleTapLogListener != null) {
-                            mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop,
-                                    event.getX() - mActivationX,
-                                    event.getY() - mActivationY);
-                        }
-                        if (withinDoubleTapSlop) {
-                            makeInactive();
-                            if (!mDoubleTapListener.onDoubleTap()) {
-                                return false;
-                            }
-                        } else {
-                            makeInactive();
-                            mTrackTouch = false;
-                        }
-                    }
-                } else {
-                    makeInactive();
-                    mTrackTouch = false;
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                makeInactive();
-                mTrackTouch = false;
-                break;
-            default:
-                break;
-        }
-        return mTrackTouch;
-    }
-
-    private void makeActive() {
-        if (!mActivated) {
-            mActivated = true;
-            mActivationListener.onActiveChanged(true);
-        }
-    }
-
-    private void makeInactive() {
-        if (mActivated) {
-            mActivated = false;
-            mActivationListener.onActiveChanged(false);
-            mView.removeCallbacks(mTapTimeoutRunnable);
-        }
-    }
-
-    private boolean isWithinTouchSlop(MotionEvent event) {
-        return Math.abs(event.getX() - mDownX) < mTouchSlop
-                && Math.abs(event.getY() - mDownY) < mTouchSlop;
-    }
-
-    public boolean isWithinDoubleTapSlop(MotionEvent event) {
-        if (!mActivated) {
-            // If we're not activated there's no double tap slop to satisfy.
-            return true;
-        }
-
-        return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop
-                && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop;
-    }
-
-    @FunctionalInterface
-    public interface ActivationListener {
-        void onActiveChanged(boolean active);
-    }
-
-    @FunctionalInterface
-    public interface DoubleTapListener {
-        boolean onDoubleTap();
-    }
-
-    @FunctionalInterface
-    public interface SlideBackListener {
-        boolean onSlideBack();
-    }
-
-    @FunctionalInterface
-    public interface DoubleTapLogListener {
-        void onDoubleTapLog(boolean accepted, float dx, float dy);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java
new file mode 100644
index 0000000..50c8e2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.view.MotionEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+
+/**
+ * Detects single and double taps on notifications.
+ */
+public class NotificationTapHelper {
+
+    public static final long DOUBLE_TAP_TIMEOUT_MS = 1200;
+
+    private final ActivationListener mActivationListener;
+    private final DoubleTapListener mDoubleTapListener;
+    private final FalsingManager mFalsingManager;
+    private final DelayableExecutor mExecutor;
+    private final SlideBackListener mSlideBackListener;
+
+    private boolean mTrackTouch;
+    private Runnable mTimeoutCancel;
+
+    private NotificationTapHelper(FalsingManager falsingManager, DelayableExecutor executor,
+            ActivationListener activationListener, DoubleTapListener doubleTapListener,
+            SlideBackListener slideBackListener) {
+        mFalsingManager = falsingManager;
+        mExecutor = executor;
+        mActivationListener = activationListener;
+        mDoubleTapListener = doubleTapListener;
+        mSlideBackListener = slideBackListener;
+    }
+
+    @VisibleForTesting
+    boolean onTouchEvent(MotionEvent event) {
+        return onTouchEvent(event, Integer.MAX_VALUE);
+    }
+
+    /** Call to have the helper process a touch event. */
+    public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
+        int action = event.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mTrackTouch = event.getY() <= maxTouchableHeight;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mTrackTouch && mFalsingManager.isFalseTap(false)) {
+                    makeInactive();
+                    mTrackTouch = false;
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                makeInactive();
+                mTrackTouch = false;
+                break;
+            case MotionEvent.ACTION_UP:
+                mTrackTouch = false;
+
+                // 1) See if we have confidence that we can activate after a single tap.
+                // 2) Else, see if it looks like a tap at all and check for a double-tap.
+                if (!mFalsingManager.isFalseTap(true)) {
+                    makeInactive();
+                    return mDoubleTapListener.onDoubleTap();
+                } else if (!mFalsingManager.isFalseTap(false)) {
+                    if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
+                        return true;
+                    }
+                    if (mTimeoutCancel == null) {
+                        // first tap
+                        makeActive();
+                        return true;
+                    } else {
+                        // second tap
+                        makeInactive();
+                        if (!mFalsingManager.isFalseDoubleTap()) {
+                            return mDoubleTapListener.onDoubleTap();
+                        }
+                    }
+                } else {
+                    makeInactive();
+                }
+                break;
+            default:
+                break;
+        }
+        return mTrackTouch;
+    }
+
+    private void makeActive() {
+        mTimeoutCancel = mExecutor.executeDelayed(this::makeInactive, DOUBLE_TAP_TIMEOUT_MS);
+        mActivationListener.onActiveChanged(true);
+    }
+
+    private void makeInactive() {
+        mActivationListener.onActiveChanged(false);
+        if (mTimeoutCancel != null) {
+            mTimeoutCancel.run();
+            mTimeoutCancel = null;
+        }
+    }
+
+    /** */
+    @FunctionalInterface
+    public interface ActivationListener {
+        /** */
+        void onActiveChanged(boolean active);
+    }
+
+    /** */
+    @FunctionalInterface
+    public interface DoubleTapListener {
+        /** */
+        boolean onDoubleTap();
+    }
+
+    /** */
+    @FunctionalInterface
+    public interface SlideBackListener {
+        /** */
+        boolean onSlideBack();
+    }
+
+    /**
+     * Injectable factory that creates a {@link NotificationTapHelper}.
+     */
+    public static class Factory {
+        private final FalsingManager mFalsingManager;
+        private final DelayableExecutor mDelayableExecutor;
+
+        @Inject
+        public Factory(FalsingManager falsingManager, @Main DelayableExecutor delayableExecutor) {
+            mFalsingManager = falsingManager;
+            mDelayableExecutor = delayableExecutor;
+        }
+
+        /** Create a {@link NotificationTapHelper} */
+        public NotificationTapHelper create(ActivationListener activationListener,
+                DoubleTapListener doubleTapListener, SlideBackListener slideBackListener) {
+            return new NotificationTapHelper(mFalsingManager, mDelayableExecutor,
+                    activationListener, doubleTapListener, slideBackListener);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 5d84245..e6ac7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -147,7 +147,6 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.charging.WirelessChargingAnimation;
-import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoMode;
@@ -1250,10 +1249,6 @@
                 message.write(SystemProperties.get("ro.serialno"));
                 message.write("\n");
 
-                PrintWriter falsingPw = new PrintWriter(message);
-                FalsingLog.dump(falsingPw);
-                falsingPw.flush();
-
                 startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND)
                                 .setType("*/*")
                                 .putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report")
@@ -2676,9 +2671,6 @@
             mLightBarController.dump(fd, pw, args);
         }
 
-        mFalsingManager.dump(pw);
-        FalsingLog.dump(pw);
-
         pw.println("SharedPreferences:");
         for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
             pw.print("  "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f9dfd7a..b912614 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -26,13 +26,17 @@
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -160,6 +164,8 @@
     private boolean mPulsing;
     private boolean mGesturalNav;
     private boolean mIsDocked;
+    private boolean mIsPortraitMode;
+    private int mScreenWidthDp;
 
     protected boolean mFirstUpdate = true;
     protected boolean mLastShowing;
@@ -174,6 +180,7 @@
     private boolean mLastPulsing;
     private int mLastBiometricMode;
     private boolean mLastLockVisible;
+    private boolean mLastLockOrientationIsPortrait;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -263,6 +270,9 @@
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
         mConfigurationController.addCallback(this);
+        mIsPortraitMode = mContext.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT;
+        mScreenWidthDp = mContext.getResources().getConfiguration().screenWidthDp;
         mGesturalNav = QuickStepContract.isGesturalMode(
                 mNavigationModeController.addListener(this));
         if (mDockManager != null) {
@@ -272,6 +282,18 @@
     }
 
     @Override
+    public void onDensityOrFontScaleChanged() {
+        hideBouncer(true /* destroyView */);
+    }
+
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        mIsPortraitMode = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
+        mScreenWidthDp = newConfig.screenWidthDp;
+        updateLockIcon();
+    }
+
+    @Override
     public void onPanelExpansionChanged(float expansion, boolean tracking) {
         // We don't want to translate the bounce when:
         // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to
@@ -317,14 +339,32 @@
         if (mLockIconContainer == null) {
             return;
         }
+
         boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                 && !mNotificationPanelViewController.isQsExpanded();
         boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
                 && !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
+        boolean orientationChange =
+                lockVisible && (mLastLockOrientationIsPortrait != mIsPortraitMode);
 
-        if (mLastLockVisible != lockVisible) {
+        if (mLastLockVisible != lockVisible || orientationChange) {
             mLastLockVisible = lockVisible;
+            mLastLockOrientationIsPortrait = mIsPortraitMode;
             if (lockVisible) {
+                FrameLayout.LayoutParams lp =
+                        (FrameLayout.LayoutParams) mLockIconContainer.getLayoutParams();
+                if (mIsPortraitMode) {
+                    lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+                } else {
+                    final int width = (int) TypedValue.applyDimension(
+                            TypedValue.COMPLEX_UNIT_DIP,
+                            mScreenWidthDp,
+                            mContext.getResources().getDisplayMetrics()) / 3;
+                    mLockIconContainer.setMinimumWidth(width);
+                    lp.gravity = Gravity.TOP | Gravity.LEFT;
+                }
+                mLockIconContainer.setLayoutParams(lp);
+
                 CrossFadeHelper.fadeIn(mLockIconContainer,
                         AppearAnimationUtils.DEFAULT_APPEAR_DURATION /* duration */,
                         0 /* delay */);
@@ -685,11 +725,6 @@
     }
 
     @Override
-    public void onDensityOrFontScaleChanged() {
-        hideBouncer(true /* destroyView */);
-    }
-
-    @Override
     public void onNavigationModeChanged(int mode) {
         boolean gesturalNav = QuickStepContract.isGesturalMode(mode);
         if (gesturalNav != mGesturalNav) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 4b4e1df..cdc5d87 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,8 +24,6 @@
 import android.view.View;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.lang.reflect.InvocationTargetException;
@@ -93,16 +91,6 @@
          * Creates the NotificationStackScrollLayout.
          */
         NotificationStackScrollLayout createNotificationStackScrollLayout();
-
-        /**
-         * Creates the QSPanel.
-         */
-        QSPanel createQSPanel();
-
-        /**
-         * Creates the QuickQSPanel.
-         */
-        QuickQSPanel createQuickQSPanel();
     }
 
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 777db95..5088a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -211,6 +211,16 @@
     }
 
     @Test
+    public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
+        mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
+        mAuthContainer.onDialogAnimatedIn();
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+                eq(null) /* credentialAttestation */);
+    }
+
+    @Test
     public void testLayoutParams_hasSecureWindowFlag() {
         final IBinder windowToken = mock(IBinder.class);
         final WindowManager.LayoutParams layoutParams =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
deleted file mode 100644
index c3c9ecc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ /dev/null
@@ -1,128 +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.classifier;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertThat;
-
-import android.provider.DeviceConfig;
-import android.testing.AndroidTestingRunner;
-import android.util.DisplayMetrics;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
-import com.android.systemui.classifier.brightline.FalsingDataProvider;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.sensors.ProximitySensor;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.utils.leaks.FakeBatteryController;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class FalsingManagerProxyTest extends LeakCheckedTest {
-    @Mock(stubOnly = true)
-    PluginManager mPluginManager;
-    @Mock(stubOnly = true)
-    ProximitySensor mProximitySensor;
-    @Mock(stubOnly = true)
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock DumpManager mDumpManager;
-    private FalsingManagerProxy mProxy;
-    private DeviceConfigProxy mDeviceConfig;
-    private FalsingDataProvider mFalsingDataProvider;
-    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
-    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
-    private DockManager mDockManager = new DockManagerFake();
-    private StatusBarStateController mStatusBarStateController =
-            new StatusBarStateControllerImpl(new UiEventLoggerFake());
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mDeviceConfig = new DeviceConfigProxyFake();
-        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
-        mFalsingDataProvider = new FalsingDataProvider(
-                new DisplayMetrics(), new FakeBatteryController(getLeakCheck()));
-    }
-
-    @After
-    public void tearDown() {
-        if (mProxy != null) {
-            mProxy.cleanup();
-        }
-    }
-
-    @Test
-    public void test_brightLineFalsingManagerDisabled() {
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
-                mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
-                mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
-        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-    }
-
-    @Test
-    public void test_brightLineFalsingManagerEnabled() throws InterruptedException {
-        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
-        mExecutor.runAllReady();
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
-                mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
-                mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
-        assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
-    }
-
-    @Test
-    public void test_brightLineFalsingManagerToggled() throws InterruptedException {
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mExecutor,
-                mProximitySensor, mDeviceConfig, mDockManager, mKeyguardUpdateMonitor,
-                mDumpManager, mUiBgExecutor, mStatusBarStateController, mFalsingDataProvider);
-        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-
-        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
-        mExecutor.runAllReady();
-        assertThat(mProxy.getInternalFalsingManager(),
-                instanceOf(BrightLineFalsingManager.class));
-
-        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
-        mExecutor.runAllReady();
-        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
index 061664b..30dddc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java
@@ -20,14 +20,18 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
+import android.view.ViewConfiguration;
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.StatusBarState;
@@ -37,6 +41,7 @@
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.FakeBatteryController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -56,6 +61,10 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
     private ProximitySensor mProximitySensor;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private ViewConfiguration mViewConfiguration;
     private SysuiStatusBarStateController mStatusBarStateController;
     private FalsingDataProvider mFalsingDataProvider;
     private FakeBatteryController mFakeBatteryController;
@@ -71,14 +80,17 @@
         dm.ydpi = 100;
         dm.widthPixels = 100;
         dm.heightPixels = 100;
-        mFalsingDataProvider = new FalsingDataProvider(dm, mFakeBatteryController);
+        mFalsingDataProvider = new FalsingDataProvider(dm, mFakeBatteryController,
+                new FakeSystemClock());
         DeviceConfigProxy deviceConfigProxy = new DeviceConfigProxyFake();
         DockManager dockManager = new DockManagerFake();
         mStatusBarStateController = new StatusBarStateControllerImpl(new UiEventLoggerFake());
         mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+        when(mResources.getDimension(R.dimen.double_tap_slop)).thenReturn(1f);
+        when(mViewConfiguration.getScaledTouchSlop()).thenReturn(1);
         mFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
-                mKeyguardUpdateMonitor, mProximitySensor, deviceConfigProxy, dockManager,
-                mStatusBarStateController);
+                mKeyguardUpdateMonitor, mProximitySensor, deviceConfigProxy, mResources,
+                mViewConfiguration, dockManager, mStatusBarStateController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
index a4d198a..69d39fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
@@ -21,6 +21,7 @@
 import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.FakeBatteryController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -44,7 +45,8 @@
         displayMetrics.widthPixels = 1000;
         displayMetrics.heightPixels = 1000;
         mFakeBatteryController = new FakeBatteryController(getLeakCheck());
-        mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController);
+        mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController,
+                new FakeSystemClock());
         mDataProvider.setInteractionType(UNLOCK);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java
new file mode 100644
index 0000000..9f3a1e4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DoubleTapClassifierTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DoubleTapClassifierTest extends ClassifierTest {
+
+    private static final int TOUCH_SLOP = 100;
+    private static final long DOUBLE_TAP_TIMEOUT_MS = 100;
+
+    private List<MotionEvent> mMotionEvents = new ArrayList<>();
+    private final Deque<List<MotionEvent>> mHistoricalMotionEvents = new LinkedList<>();
+
+    @Mock
+    private FalsingDataProvider mDataProvider;
+    @Mock
+    private SingleTapClassifier mSingleTapClassifier;
+    private DoubleTapClassifier mClassifier;
+
+    @Before
+    public void setup() {
+        super.setup();
+        MockitoAnnotations.initMocks(this);
+        mClassifier = new DoubleTapClassifier(mDataProvider, mSingleTapClassifier, TOUCH_SLOP,
+                DOUBLE_TAP_TIMEOUT_MS);
+        doReturn(mHistoricalMotionEvents).when(mDataProvider).getHistoricalMotionEvents();
+    }
+
+    @After
+    public void tearDown() {
+        for (MotionEvent motionEvent : mMotionEvents) {
+            motionEvent.recycle();
+        }
+
+        mMotionEvents.clear();
+        super.tearDown();
+    }
+
+
+    @Test
+    public void testSingleTap() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat("Single tap recognized as a valid double tap", result,  is(true));
+    }
+
+    @Test
+    public void testDoubleTap() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        archiveMotionEvents();
+
+        addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP, TOUCH_SLOP);
+        addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat(mClassifier.getReason(), result, is(false));
+    }
+
+    @Test
+    public void testBadFirstTap() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(false, true);
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        archiveMotionEvents();
+
+        addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat("Bad first touch allowed", result, is(true));
+    }
+
+    @Test
+    public void testBadSecondTap() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(true, false);
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        archiveMotionEvents();
+
+        addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat("Bad second touch allowed", result, is(true));
+    }
+
+    @Test
+    public void testBadTouchSlop() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        archiveMotionEvents();
+
+        addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP + 1, TOUCH_SLOP);
+        addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP + 1);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat("Sloppy second touch allowed", result, is(true));
+    }
+
+    @Test
+    public void testBadTouchSlow() {
+        when(mSingleTapClassifier.isTap(anyList())).thenReturn(true);
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        archiveMotionEvents();
+
+        addMotionEvent(DOUBLE_TAP_TIMEOUT_MS + 1, DOUBLE_TAP_TIMEOUT_MS + 1,
+                MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(DOUBLE_TAP_TIMEOUT_MS + 1, DOUBLE_TAP_TIMEOUT_MS + 2,
+                MotionEvent.ACTION_UP, 1, 1);
+
+        boolean result = mClassifier.isFalseTouch();
+        assertThat("Slow second tap allowed", result, is(true));
+    }
+
+    private void addMotionEvent(long downMs, long eventMs, int action, int x, int y) {
+        MotionEvent ev = MotionEvent.obtain(downMs, eventMs, action, x, y, 0);
+        mMotionEvents.add(ev);
+        when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
+    }
+
+    private void archiveMotionEvents() {
+        mHistoricalMotionEvents.addFirst(mMotionEvents);
+        doReturn(mHistoricalMotionEvents).when(mDataProvider).getHistoricalMotionEvents();
+        mMotionEvents = new ArrayList<>();
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
index f13bc73..be38f4419 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java
@@ -26,6 +26,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.FakeBatteryController;
 
 import org.junit.After;
@@ -51,7 +52,8 @@
         displayMetrics.ydpi = 100;
         displayMetrics.widthPixels = 1000;
         displayMetrics.heightPixels = 1000;
-        mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController);
+        mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController,
+                new FakeSystemClock());
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java
new file mode 100644
index 0000000..642b077
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/SingleTapClassifierTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SingleTapClassifierTest extends ClassifierTest {
+
+    private static final int TOUCH_SLOP = 100;
+
+    private final List<MotionEvent> mMotionEvents = new ArrayList<>();
+
+    @Mock
+    private FalsingDataProvider mDataProvider;
+    private SingleTapClassifier mClassifier;
+
+    @Before
+    public void setup() {
+        super.setup();
+        MockitoAnnotations.initMocks(this);
+        mClassifier = new SingleTapClassifier(mDataProvider, TOUCH_SLOP);
+    }
+
+    @After
+    public void tearDown() {
+        for (MotionEvent motionEvent : mMotionEvents) {
+            motionEvent.recycle();
+        }
+
+        mMotionEvents.clear();
+        super.tearDown();
+    }
+
+
+    @Test
+    public void testSimpleTap_XSlop() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(false));
+
+        mMotionEvents.clear();
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP + 2, 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(false));
+
+    }
+
+    @Test
+    public void testSimpleTap_YSlop() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP);
+
+        assertThat(mClassifier.isFalseTouch(), is(false));
+
+        mMotionEvents.clear();
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP + 2);
+
+        assertThat(mClassifier.isFalseTouch(), is(false));
+    }
+
+
+    @Test
+    public void testFalseTap_XSlop() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP + 1, 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(true));
+
+        mMotionEvents.clear();
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP - 1, 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(true));
+
+    }
+
+    @Test
+    public void testFalseTap_YSlop() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(true));
+
+        mMotionEvents.clear();
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP - 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(true));
+    }
+
+    @Test
+    public void testLargeMovementFalses() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_MOVE, 1, TOUCH_SLOP + 1);
+        addMotionEvent(0, 2, MotionEvent.ACTION_UP, 1, 1);
+
+        assertThat(mClassifier.isFalseTouch(), is(true));
+    }
+
+    @Test
+    public void testDirectlySuppliedMotionEvents() {
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
+
+        assertThat(mClassifier.isTap(mMotionEvents), is(true));
+
+        addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
+        addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
+
+        assertThat(mClassifier.isTap(mMotionEvents), is(false));
+
+    }
+
+    private void addMotionEvent(long downMs, long eventMs, int action, int x, int y) {
+        MotionEvent ev = MotionEvent.obtain(downMs, eventMs, action, x, y, 0);
+        mMotionEvents.add(ev);
+        when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java
new file mode 100644
index 0000000..1dfffb2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/TimeLimitedMotionEventBufferTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.classifier.brightline;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
+
+    private static final long MAX_AGE_MS = 100;
+
+    private TimeLimitedMotionEventBuffer mBuffer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
+    }
+
+    @After
+    public void tearDown() {
+        for (MotionEvent motionEvent : mBuffer) {
+            motionEvent.recycle();
+        }
+        mBuffer.clear();
+    }
+
+    @Test
+    public void testAllEventsRetained() {
+        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+
+        assertThat(mBuffer.size(), is(4));
+    }
+
+    @Test
+    public void testOlderEventsRemoved() {
+        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventC = MotionEvent.obtain(
+                0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventD = MotionEvent.obtain(
+                0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        assertThat(mBuffer.size(), is(2));
+
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+        assertThat(mBuffer.size(), is(2));
+
+        assertThat(mBuffer.get(0), is(eventC));
+        assertThat(mBuffer.get(1), is(eventD));
+    }
+
+    @Test
+    public void testFullyExpired() {
+        MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mBuffer.add(eventA);
+        mBuffer.add(eventB);
+        mBuffer.add(eventC);
+        mBuffer.add(eventD);
+
+        assertThat(mBuffer.isFullyExpired(2), is(false));
+        assertThat(mBuffer.isFullyExpired(6), is(true));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0fe44ad..0dc268a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -42,7 +42,9 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.util.animation.DisappearParameters;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -75,6 +77,8 @@
     @Mock
     private MetricsLogger mMetricsLogger;
     private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QSLogger mQSLogger;
     private DumpManager mDumpManager = new DumpManager();
     @Mock
     QSTileImpl mQSTile;
@@ -89,9 +93,10 @@
     private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
-                MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
-            super(view, host, qsCustomizerController, mediaHost,
-                    metricsLogger, uiEventLogger, dumpManager);
+                MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
+                DumpManager dumpManager) {
+            super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
+                    qsLogger, dumpManager);
         }
 
         @Override
@@ -109,13 +114,17 @@
         when(mQSPanel.openPanelEvent()).thenReturn(QSEvent.QS_PANEL_EXPANDED);
         when(mQSPanel.closePanelEvent()).thenReturn(QSEvent.QS_PANEL_COLLAPSED);
         when(mQSPanel.createRegularTileLayout()).thenReturn(mPagedTileLayout);
+        when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+        when(mQSTile.getTileSpec()).thenReturn("dnd");
         when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
         when(mQSTileHost.createTileView(eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
         when(mQSTileRevealControllerFactory.create(any(), any()))
                 .thenReturn(mQSTileRevealController);
+        when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
-                mQSCustomizerController, mMediaHost, mMetricsLogger, mUiEventLogger, mDumpManager);
+                mQSCustomizerController, mMediaHost,
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
 
         mController.init();
         reset(mQSTileRevealController);
@@ -125,9 +134,9 @@
     public void testSetRevealExpansion_preAttach() {
         mController.onViewDetached();
 
-        QSPanelControllerBase<QSPanel> controller = new QSPanelControllerBase<QSPanel>(
-                mQSPanel, mQSTileHost, mQSCustomizerController, mMediaHost, mMetricsLogger,
-                mUiEventLogger, mDumpManager) {
+        QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
+                mQSTileHost, mQSCustomizerController, mMediaHost,
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
             @Override
             protected QSTileRevealController createTileRevealController() {
                 return mQSTileRevealController;
@@ -159,14 +168,18 @@
 
     @Test
     public void testSetExpanded_Metrics() {
+        when(mQSPanel.isExpanded()).thenReturn(false);
         mController.setExpanded(true);
         verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+        verify(mQSLogger).logPanelExpanded(true, mQSPanel.getDumpableTag());
         assertEquals(1, mUiEventLogger.numLogs());
         assertEquals(QSEvent.QS_PANEL_EXPANDED.getId(), mUiEventLogger.eventId(0));
         mUiEventLogger.getLogs().clear();
 
+        when(mQSPanel.isExpanded()).thenReturn(true);
         mController.setExpanded(false);
         verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+        verify(mQSLogger).logPanelExpanded(false, mQSPanel.getDumpableTag());
         assertEquals(1, mUiEventLogger.numLogs());
         assertEquals(QSEvent.QS_PANEL_COLLAPSED.getId(), mUiEventLogger.eventId(0));
         mUiEventLogger.getLogs().clear();
@@ -195,4 +208,14 @@
         assertEquals(expected, w.getBuffer().toString());
     }
 
+    @Test
+    public void setListening() {
+        mController.setListening(true);
+        verify(mQSLogger).logAllTilesChangeListening(true, "QSPanel", "dnd");
+        verify(mPagedTileLayout).setListening(true, mUiEventLogger);
+
+        mController.setListening(false);
+        verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
+        verify(mPagedTileLayout).setListening(false, mUiEventLogger);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 9090b9b..a6c2d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
@@ -77,6 +78,8 @@
     @Mock
     private QSSecurityFooter mQSSecurityFooter;
     @Mock
+    private QSLogger mQSLogger;
+    @Mock
     private BrightnessController.Factory mBrightnessControllerFactory;
     @Mock
     private BrightnessController mBrightnessController;
@@ -91,7 +94,6 @@
     @Mock
     PagedTileLayout mPagedTileLayout;
 
-
     private QSPanelController mController;
 
     @Before
@@ -112,9 +114,9 @@
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
 
         mController = new QSPanelController(mQSPanel, mQSSecurityFooter, mTunerService,
-                mQSTileHost, mQSCustomizerController, mMediaHost, mQSTileRevealControllerFactory,
-                mDumpManager, mMetricsLogger, mUiEventLogger, mBrightnessControllerFactory,
-                mToggleSliderViewControllerFactory);
+                mQSTileHost, mQSCustomizerController, true, mMediaHost,
+                mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
+                mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory);
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 450ffac..a726181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -17,13 +17,10 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -32,18 +29,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.animation.DisappearParameters;
-import com.android.systemui.util.animation.UniqueObjectHostView;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,7 +48,6 @@
 @SmallTest
 public class QSPanelTest extends SysuiTestCase {
 
-    private MetricsLogger mMetricsLogger;
     private TestableLooper mTestableLooper;
     private QSPanel mQsPanel;
     @Mock
@@ -75,30 +64,23 @@
     @Mock
     private QSTileView mQSTileView;
     @Mock
-    private MediaHost mMediaHost;
-    @Mock
     private ActivityStarter mActivityStarter;
-    private UiEventLoggerFake mUiEventLogger;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
 
-        // Dependencies for QSSecurityFooter
-        mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
-        mDependency.injectMockDependency(SecurityController.class);
-        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
-        mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class));
-        when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext()));
-        when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+//        // Dependencies for QSSecurityFooter
+//        mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+//        mDependency.injectMockDependency(SecurityController.class);
+//        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+//        mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class));
         mDndTileRecord.tile = dndTile;
         mDndTileRecord.tileView = mQSTileView;
 
-        mUiEventLogger = new UiEventLoggerFake();
         mTestableLooper.runWithLooper(() -> {
-            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
-            mQsPanel = new QSPanel(mContext, null, mQSLogger, mMediaHost, mUiEventLogger);
+            mQsPanel = new QSPanel(mContext, null);
             mQsPanel.onFinishInflate();
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
@@ -113,15 +95,6 @@
     }
 
     @Test
-    public void testSetExpanded_Metrics() {
-        mQsPanel.setExpanded(true);
-        verify(mQSLogger).logPanelExpanded(true, mQsPanel.getDumpableTag());
-
-        mQsPanel.setExpanded(false);
-        verify(mQSLogger).logPanelExpanded(false, mQsPanel.getDumpableTag());
-    }
-
-    @Test
     public void testOpenDetailsWithExistingTile_NoException() {
         mTestableLooper.processAllMessages();
         mQsPanel.openDetails(dndTile);
@@ -131,15 +104,6 @@
     }
 
     @Test
-    public void setListening() {
-        mQsPanel.setListening(true, "dnd");
-        verify(mQSLogger).logAllTilesChangeListening(true, mQsPanel.getDumpableTag(), "dnd");
-
-        mQsPanel.setListening(false, "dnd");
-        verify(mQSLogger).logAllTilesChangeListening(false, mQsPanel.getDumpableTag(), "dnd");
-    }
-
-    @Test
     public void testOpenDetailsWithNullParameter_NoException() {
         mTestableLooper.processAllMessages();
         mQsPanel.openDetails(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
new file mode 100644
index 0000000..3f2b4da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.MediaHost
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.customize.QSCustomizerController
+import com.android.systemui.qs.logging.QSLogger
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QuickQSPanelControllerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var quickQSPanel: QuickQSPanel
+    @Mock
+    private lateinit var qsTileHost: QSTileHost
+    @Mock
+    private lateinit var qsCustomizerController: QSCustomizerController
+    @Mock
+    private lateinit var mediaHost: MediaHost
+    @Mock
+    private lateinit var metricsLogger: MetricsLogger
+    private val uiEventLogger = UiEventLoggerFake()
+    @Mock
+    private lateinit var qsLogger: QSLogger
+    private val dumpManager = DumpManager()
+    @Mock
+    private lateinit var tile: QSTile
+    @Mock
+    private lateinit var tileLayout: TileLayout
+
+    private lateinit var controller: QuickQSPanelController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
+        `when`(quickQSPanel.dumpableTag).thenReturn("")
+
+        controller = QuickQSPanelController(
+                quickQSPanel,
+                qsTileHost,
+                qsCustomizerController,
+                false,
+                mediaHost,
+                metricsLogger,
+                uiEventLogger,
+                qsLogger,
+                dumpManager
+        )
+
+        controller.init()
+    }
+
+    @After
+    fun tearDown() {
+        controller.onViewDetached()
+    }
+
+    @Test
+    fun testTileSublistWithFewerTiles_noCrash() {
+        `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+
+        `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+
+        controller.setTiles()
+    }
+
+    @Test
+    fun testTileSublistWithTooManyTiles() {
+        val limit = 3
+        `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
+        `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+
+        controller.setTiles()
+
+        verify(quickQSPanel, times(limit)).addTile(any())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 6c7c20a..54cee84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -75,7 +75,7 @@
     public void testSetListening_CallsSetListeningOnTile() {
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
         mTileLayout.addTile(tileRecord);
-        mTileLayout.setListening(true);
+        mTileLayout.setListening(true, null);
         verify(tileRecord.tile, times(1)).setListening(mTileLayout, true);
     }
 
@@ -83,14 +83,14 @@
     public void testSetListening_SameValueIsNoOp() {
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
         mTileLayout.addTile(tileRecord);
-        mTileLayout.setListening(false);
+        mTileLayout.setListening(false, null);
         verify(tileRecord.tile, times(1)).setListening(any(), anyBoolean());
     }
 
     @Test
     public void testSetListening_ChangesValueForAddingFutureTiles() {
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
-        mTileLayout.setListening(true);
+        mTileLayout.setListening(true, null);
         mTileLayout.addTile(tileRecord);
         verify(tileRecord.tile, times(1)).setListening(mTileLayout, true);
     }
@@ -98,7 +98,7 @@
     @Test
     public void testRemoveTile_CallsSetListeningFalseOnTile() {
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
-        mTileLayout.setListening(true);
+        mTileLayout.setListening(true, null);
         mTileLayout.addTile(tileRecord);
         mTileLayout.removeTile(tileRecord);
         verify(tileRecord.tile, times(1)).setListening(mTileLayout, false);
@@ -108,7 +108,7 @@
     public void testRemoveAllViews_CallsSetListeningFalseOnAllTiles() {
         QSPanelControllerBase.TileRecord tileRecord1 = createTileRecord();
         QSPanelControllerBase.TileRecord tileRecord2 = createTileRecord();
-        mTileLayout.setListening(true);
+        mTileLayout.setListening(true, null);
         mTileLayout.addTile(tileRecord1);
         mTileLayout.addTile(tileRecord2);
         mTileLayout.removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 37e8218..c426c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -15,6 +15,7 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
+import static android.provider.Settings.Global.SHOW_NEW_NOTIF_DISMISS;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -96,6 +97,7 @@
     @Test
     public void testNoAppOpsInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
@@ -108,6 +110,7 @@
     @Test
     public void testNoSnoozeInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
@@ -120,6 +123,7 @@
     @Test
     public void testSnoozeInSlowSwipe() {
         Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
         row.createMenu(mRow, null);
@@ -130,6 +134,19 @@
     }
 
     @Test
+    public void testSlowSwipe_newDismiss() {
+        Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
+        Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
+        row.createMenu(mRow, null);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // Clear menu
+        assertEquals(0, container.getChildCount());
+    }
+
+    @Test
     public void testIsSnappedAndOnSameSide() {
         NotificationMenuRow row = Mockito.spy(
                 new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index e9e6b3e..4841b3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -231,6 +231,7 @@
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
+        when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
                 mDisplayMetrics);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
index df1233a..4ed2746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
@@ -16,17 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
-import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
 import android.view.View;
@@ -36,48 +31,47 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class DoubleTapHelperTest extends SysuiTestCase {
+public class NotificationTapHelperTest extends SysuiTestCase {
 
-    private DoubleTapHelper mDoubleTapHelper;
-    private int mTouchSlop;
-    private int mDoubleTouchSlop;
+    private NotificationTapHelper mNotificationTapHelper;
+    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
+    private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
     @Mock private View mView;
-    @Mock private DoubleTapHelper.ActivationListener mActivationListener;
-    @Mock private DoubleTapHelper.DoubleTapListener mDoubleTapListener;
-    @Mock private DoubleTapHelper.SlideBackListener mSlideBackListener;
-    @Mock private DoubleTapHelper.DoubleTapLogListener mDoubleTapLogListener;
+    @Mock private NotificationTapHelper.ActivationListener mActivationListener;
+    @Mock private NotificationTapHelper.DoubleTapListener mDoubleTapListener;
+    @Mock private NotificationTapHelper.SlideBackListener mSlideBackListener;
     @Mock private Resources mResources;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
-        // The double tap slop has to be less than the regular slop, otherwise it has no effect.
-        mDoubleTouchSlop = mTouchSlop - 1;
         when(mView.getContext()).thenReturn(mContext);
         when(mView.getResources()).thenReturn(mResources);
         when(mResources.getDimension(R.dimen.double_tap_slop))
-                .thenReturn((float) mDoubleTouchSlop);
+                .thenReturn((float) ViewConfiguration.get(mContext).getScaledTouchSlop() - 1);
 
-        mDoubleTapHelper = new DoubleTapHelper(mView,
-                                               mActivationListener,
-                                               mDoubleTapListener,
-                                               mSlideBackListener, mDoubleTapLogListener);
+        mFalsingManager.setFalseRobustTap(true);  // Test double tapping most of the time.
+
+        mNotificationTapHelper = new NotificationTapHelper.Factory(mFalsingManager, mFakeExecutor)
+                .create(mActivationListener, mDoubleTapListener, mSlideBackListener);
     }
 
     @Test
     public void testDoubleTap_success() {
-        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeA = 100;
         long downtimeB = downtimeA + 100;
 
         MotionEvent evDownA = MotionEvent.obtain(downtimeA,
@@ -105,16 +99,13 @@
                                                1,
                                                0);
 
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
         verify(mActivationListener).onActiveChanged(true);
-        verify(mView).postDelayed(any(Runnable.class), anyLong());
-        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
         verify(mDoubleTapListener, never()).onDoubleTap();
 
-        mDoubleTapHelper.onTouchEvent(evDownB);
-        mDoubleTapHelper.onTouchEvent(evUpB);
-        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        mNotificationTapHelper.onTouchEvent(evDownB);
+        mNotificationTapHelper.onTouchEvent(evUpB);
         verify(mDoubleTapListener).onDoubleTap();
 
         evDownA.recycle();
@@ -125,7 +116,7 @@
 
     @Test
     public void testSingleTap_timeout() {
-        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeA = 100;
 
         MotionEvent evDownA = MotionEvent.obtain(downtimeA,
                                                  downtimeA,
@@ -140,21 +131,19 @@
                                                1,
                                                0);
 
-        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
         verify(mActivationListener).onActiveChanged(true);
-        verify(mView).postDelayed(runnableCaptor.capture(), anyLong());
-        runnableCaptor.getValue().run();
-        verify(mActivationListener).onActiveChanged(true);
+        drainExecutor();
+        verify(mActivationListener).onActiveChanged(false);
 
         evDownA.recycle();
         evUpA.recycle();
     }
 
     @Test
-    public void testSingleTap_slop() {
-        long downtimeA = SystemClock.uptimeMillis();
+    public void testSingleTap_falsed() {
+        long downtimeA = 100;
 
         MotionEvent evDownA = MotionEvent.obtain(downtimeA,
                                                  downtimeA,
@@ -165,13 +154,13 @@
         MotionEvent evUpA = MotionEvent.obtain(downtimeA,
                                                downtimeA + 1,
                                                MotionEvent.ACTION_UP,
-                                               1 + mTouchSlop,
+                                               1,
                                                1,
                                                0);
 
-        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
+        mFalsingManager.setFalseTap(true);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
         verify(mActivationListener, never()).onActiveChanged(true);
         verify(mDoubleTapListener, never()).onDoubleTap();
 
@@ -180,8 +169,8 @@
     }
 
     @Test
-    public void testDoubleTap_slop() {
-        long downtimeA = SystemClock.uptimeMillis();
+    public void testDoubleTap_falsed() {
+        long downtimeA = 100;
         long downtimeB = downtimeA + 100;
 
         MotionEvent evDownA = MotionEvent.obtain(downtimeA,
@@ -206,17 +195,17 @@
                                                downtimeB + 1,
                                                MotionEvent.ACTION_UP,
                                                1,
-                                               1 + mDoubleTouchSlop,
+                                               1,
                                                0);
 
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
-        verify(mActivationListener).onActiveChanged(true);
-        verify(mView).postDelayed(any(Runnable.class), anyLong());
+        mFalsingManager.setFalseDoubleTap(true);
 
-        mDoubleTapHelper.onTouchEvent(evDownB);
-        mDoubleTapHelper.onTouchEvent(evUpB);
-        verify(mDoubleTapLogListener).onDoubleTapLog(false, 0, mDoubleTouchSlop);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener).onActiveChanged(true);
+
+        mNotificationTapHelper.onTouchEvent(evDownB);
+        mNotificationTapHelper.onTouchEvent(evUpB);
         verify(mActivationListener).onActiveChanged(false);
         verify(mDoubleTapListener, never()).onDoubleTap();
 
@@ -228,8 +217,7 @@
 
     @Test
     public void testSlideBack() {
-        long downtimeA = SystemClock.uptimeMillis();
-        long downtimeB = downtimeA + 100;
+        long downtimeA = 100;
 
         MotionEvent evDownA = MotionEvent.obtain(downtimeA,
                                                  downtimeA,
@@ -243,42 +231,24 @@
                                                1,
                                                1,
                                                0);
-        MotionEvent evDownB = MotionEvent.obtain(downtimeB,
-                                                 downtimeB,
-                                                 MotionEvent.ACTION_DOWN,
-                                                 1,
-                                                 1,
-                                                 0);
-        MotionEvent evUpB = MotionEvent.obtain(downtimeB,
-                                               downtimeB + 1,
-                                               MotionEvent.ACTION_UP,
-                                               1,
-                                               1,
-                                               0);
 
         when(mSlideBackListener.onSlideBack()).thenReturn(true);
 
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
         verify(mActivationListener, never()).onActiveChanged(true);
         verify(mActivationListener, never()).onActiveChanged(false);
         verify(mDoubleTapListener, never()).onDoubleTap();
-        mDoubleTapHelper.onTouchEvent(evDownB);
-        mDoubleTapHelper.onTouchEvent(evUpB);
-        verify(mActivationListener, never()).onActiveChanged(true);
-        verify(mActivationListener, never()).onActiveChanged(false);
-        verify(mDoubleTapListener, never()).onDoubleTap();
+        verify(mSlideBackListener).onSlideBack();
 
         evDownA.recycle();
         evUpA.recycle();
-        evDownB.recycle();
-        evUpB.recycle();
     }
 
 
     @Test
     public void testMoreThanTwoTaps() {
-        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeA = 100;
         long downtimeB = downtimeA + 100;
         long downtimeC = downtimeB + 100;
         long downtimeD = downtimeC + 100;
@@ -332,33 +302,26 @@
                                                1,
                                                0);
 
-        mDoubleTapHelper.onTouchEvent(evDownA);
-        mDoubleTapHelper.onTouchEvent(evUpA);
+        mNotificationTapHelper.onTouchEvent(evDownA);
+        mNotificationTapHelper.onTouchEvent(evUpA);
         verify(mActivationListener).onActiveChanged(true);
-        verify(mView).postDelayed(any(Runnable.class), anyLong());
-        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
         verify(mDoubleTapListener, never()).onDoubleTap();
 
-        mDoubleTapHelper.onTouchEvent(evDownB);
-        mDoubleTapHelper.onTouchEvent(evUpB);
-        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        mNotificationTapHelper.onTouchEvent(evDownB);
+        mNotificationTapHelper.onTouchEvent(evUpB);
         verify(mDoubleTapListener).onDoubleTap();
 
         reset(mView);
         reset(mActivationListener);
-        reset(mDoubleTapLogListener);
         reset(mDoubleTapListener);
 
-        mDoubleTapHelper.onTouchEvent(evDownC);
-        mDoubleTapHelper.onTouchEvent(evUpC);
+        mNotificationTapHelper.onTouchEvent(evDownC);
+        mNotificationTapHelper.onTouchEvent(evUpC);
         verify(mActivationListener).onActiveChanged(true);
-        verify(mView).postDelayed(any(Runnable.class), anyLong());
-        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
         verify(mDoubleTapListener, never()).onDoubleTap();
 
-        mDoubleTapHelper.onTouchEvent(evDownD);
-        mDoubleTapHelper.onTouchEvent(evUpD);
-        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        mNotificationTapHelper.onTouchEvent(evDownD);
+        mNotificationTapHelper.onTouchEvent(evUpD);
         verify(mDoubleTapListener).onDoubleTap();
 
         evDownA.recycle();
@@ -366,4 +329,9 @@
         evDownB.recycle();
         evUpB.recycle();
     }
+
+    private void drainExecutor() {
+        mFakeExecutor.advanceClockToLast();
+        mFakeExecutor.runAllReady();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9832d31..83030ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -33,6 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.widget.FrameLayout;
 
 import androidx.test.filters.SmallTest;
 
@@ -103,6 +104,7 @@
         when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
         when(mLockIconContainer.animate()).thenReturn(mock(ViewPropertyAnimator.class,
                 RETURNS_DEEP_STUBS));
+        when(mLockIconContainer.getLayoutParams()).thenReturn(mock(FrameLayout.LayoutParams.class));
 
         when(mKeyguardBouncerFactory.create(
                 any(ViewGroup.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
index ecfb357..d8271e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -28,17 +28,16 @@
  * backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
  *
  * Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
- * every call to {@link #advanceTime(long)}. Thread time always lags by 50% of the uptime
+ * every call to {@link #advanceTime}. Thread time always lags by 50% of the uptime
  * advancement to simulate time loss due to scheduling.
  */
 public class FakeSystemClock implements SystemClock {
     private long mUptimeMillis = 10000;
     private long mElapsedRealtime = 10000;
     private long mCurrentThreadTimeMillis = 10000;
-
     private long mCurrentTimeMillis = 1555555500000L;
-
     private final List<ClockTickListener> mListeners = new ArrayList<>();
+
     @Override
     public long uptimeMillis() {
         return mUptimeMillis;
@@ -72,21 +71,38 @@
         mCurrentTimeMillis = millis;
     }
 
-    public void advanceTime(long uptime) {
-        advanceTime(uptime, 0);
+    /**
+     * Advances the time tracked by the fake clock and notifies any listeners that the time has
+     * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+     *
+     * All tracked times increment by [millis], with the exception of currentThreadTimeMillis,
+     * which advances by [millis] * 0.5
+     */
+    public void advanceTime(long millis) {
+        advanceTime(millis, 0);
     }
 
-    public void advanceTime(long uptime, long sleepTime) {
-        if (uptime < 0 || sleepTime < 0) {
+    /**
+     * Advances the time tracked by the fake clock and notifies any listeners that the time has
+     * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+     *
+     * The tracked times change as follows:
+     * - uptimeMillis increments by [awakeMillis]
+     * - currentThreadTimeMillis increments by [awakeMillis] * 0.5
+     * - elapsedRealtime increments by [awakeMillis] + [sleepMillis]
+     * - currentTimeMillis increments by [awakeMillis] + [sleepMillis]
+     */
+    public void advanceTime(long awakeMillis, long sleepMillis) {
+        if (awakeMillis < 0 || sleepMillis < 0) {
             throw new IllegalArgumentException("Time cannot go backwards");
         }
 
-        if (uptime > 0 || sleepTime > 0) {
-            mUptimeMillis += uptime;
-            mElapsedRealtime += uptime + sleepTime;
-            mCurrentTimeMillis += uptime + sleepTime;
+        if (awakeMillis > 0 || sleepMillis > 0) {
+            mUptimeMillis += awakeMillis;
+            mElapsedRealtime += awakeMillis + sleepMillis;
+            mCurrentTimeMillis += awakeMillis + sleepMillis;
 
-            mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
+            mCurrentThreadTimeMillis += Math.ceil(awakeMillis * 0.5);
 
             for (ClockTickListener listener : mListeners) {
                 listener.onClockTick();
@@ -105,6 +121,4 @@
     public interface ClockTickListener {
         void onClockTick();
     }
-
-    private static final long START_TIME = 10000;
 }
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 15bd4dc..ae024ff 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -332,5 +332,9 @@
     // Notify the user that the admin suspended personal apps on the device.
     // Package: android
     NOTE_PERSONAL_APPS_SUSPENDED = 1003;
+
+    // Notify the user that window magnification is available.
+    // package: android
+    NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004;
   }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 857ac6a..be7643e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -37,6 +37,7 @@
 import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
 import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
+import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
@@ -561,8 +562,9 @@
                     detectControlGestures, triggerable, displayId);
         } else {
             magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext,
-                    mAms.getFullScreenMagnificationController(), mAms::onMagnificationScaleChanged,
-                    detectControlGestures, triggerable, displayId);
+                    mAms.getFullScreenMagnificationController(),
+                    mAms::onMagnificationScaleChanged, detectControlGestures, triggerable,
+                    new WindowMagnificationPromptController(displayContext, mUserId), displayId);
         }
         return magnificationGestureHandler;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index d50e9d7..efb9d87 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -137,6 +137,7 @@
     @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
 
     private final ScreenStateReceiver mScreenStateReceiver;
+    private final WindowMagnificationPromptController mPromptController;
 
     /**
      * {@code true} if this detector should detect and respond to triple-tap
@@ -178,6 +179,7 @@
             ScaleChangedListener listener,
             boolean detectTripleTap,
             boolean detectShortcutTrigger,
+            @NonNull WindowMagnificationPromptController promptController,
             int displayId) {
         super(listener);
         if (DEBUG_ALL) {
@@ -186,6 +188,7 @@
                             + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
         }
         mFullScreenMagnificationController = fullScreenMagnificationController;
+        mPromptController = promptController;
         mDisplayId = displayId;
 
         mDelegatingState = new DelegatingState();
@@ -195,7 +198,6 @@
 
         mDetectTripleTap = detectTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
-
         if (mDetectShortcutTrigger) {
             mScreenStateReceiver = new ScreenStateReceiver(context, this);
             mScreenStateReceiver.register();
@@ -264,6 +266,7 @@
         if (mScreenStateReceiver != null) {
             mScreenStateReceiver.unregister();
         }
+        mPromptController.onDestroy();
         // Check if need to reset when MagnificationGestureHandler is the last magnifying service.
         mFullScreenMagnificationController.resetAllIfNeeded(
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -278,6 +281,7 @@
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
             } else {
+                mPromptController.showNotificationIfNeeded();
                 mDetectingState.toggleShortcutTriggered();
             }
         }
@@ -950,6 +954,7 @@
             if (mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
                 zoomOff();
             } else {
+                mPromptController.showNotificationIfNeeded();
                 zoomOn(up.getX(), up.getY());
             }
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
new file mode 100644
index 0000000..7212207
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ActivityOptions;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * A class to show notification to prompt the user that this feature is available.
+ */
+public class WindowMagnificationPromptController {
+
+    private static final Uri MAGNIFICATION_WINDOW_MODE_PROMPT_URI = Settings.Secure.getUriFor(
+            ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+    @VisibleForTesting
+    static final String ACTION_DISMISS =
+            "com.android.server.accessibility.magnification.action.DISMISS";
+    @VisibleForTesting
+    static final String ACTION_TURN_ON_IN_SETTINGS =
+            "com.android.server.accessibility.magnification.action.TURN_ON_IN_SETTINGS";
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final ContentObserver mContentObserver;
+    private final int mUserId;
+    @VisibleForTesting
+    BroadcastReceiver mNotificationActionReceiver;
+
+    private boolean mNeedToShowNotification;
+
+    @MainThread
+    public WindowMagnificationPromptController(@NonNull Context context, int userId) {
+        mContext = context;
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+        mUserId = userId;
+        mContentObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+                onPromptSettingsValueChanged();
+            }
+        };
+        context.getContentResolver().registerContentObserver(MAGNIFICATION_WINDOW_MODE_PROMPT_URI,
+                false, mContentObserver, mUserId);
+        mNeedToShowNotification = isWindowMagnificationPromptEnabled();
+    }
+
+    @VisibleForTesting
+    protected void onPromptSettingsValueChanged() {
+        final boolean needToShowNotification = isWindowMagnificationPromptEnabled();
+        if (mNeedToShowNotification == needToShowNotification) {
+            return;
+        }
+        mNeedToShowNotification = needToShowNotification;
+        if (!mNeedToShowNotification) {
+            unregisterReceiverIfNeeded();
+            mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+        }
+    }
+
+    /**
+     * Shows the prompt notification that could bring users to magnification settings if necessary.
+     */
+    @MainThread
+    void showNotificationIfNeeded() {
+        if (!mNeedToShowNotification) return;
+
+        final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+                SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION);
+        notificationBuilder.setSmallIcon(R.drawable.ic_settings_24dp)
+                .setContentTitle(mContext.getString(R.string.window_magnification_prompt_title))
+                .setContentText(mContext.getString(R.string.window_magnification_prompt_content))
+                .setLargeIcon(Icon.createWithResource(mContext,
+                        R.drawable.ic_accessibility_magnification))
+                .setTicker(mContext.getString(R.string.window_magnification_prompt_title))
+                .setOnlyAlertOnce(true)
+                .setDeleteIntent(createPendingIntent(ACTION_DISMISS))
+                .setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS))
+                .setActions(buildTurnOnAction(), buildDismissAction());
+        mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE,
+                notificationBuilder.build());
+        registerReceiverIfNeeded();
+    }
+
+    /**
+     * Called when this object is not used anymore to release resources if necessary.
+     */
+    @VisibleForTesting
+    @MainThread
+    public void onDestroy() {
+        dismissNotification();
+        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+    }
+
+    private boolean isWindowMagnificationPromptEnabled() {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId) == 1;
+    }
+
+    private Notification.Action buildTurnOnAction() {
+        return new Notification.Action.Builder(null,
+                mContext.getString(R.string.turn_on_magnification_settings_action),
+                createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build();
+    }
+
+    private Notification.Action buildDismissAction() {
+        return new Notification.Action.Builder(null, mContext.getString(R.string.dismiss_action),
+                createPendingIntent(ACTION_DISMISS)).build();
+    }
+
+    private PendingIntent createPendingIntent(String action) {
+        final Intent intent = new Intent(action);
+        intent.setPackage(mContext.getPackageName());
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    private void registerReceiverIfNeeded() {
+        if (mNotificationActionReceiver != null) {
+            return;
+        }
+        mNotificationActionReceiver = new NotificationActionReceiver();
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_DISMISS);
+        intentFilter.addAction(ACTION_TURN_ON_IN_SETTINGS);
+        mContext.registerReceiver(mNotificationActionReceiver, intentFilter,
+                Manifest.permission.MANAGE_ACCESSIBILITY, null);
+    }
+
+    private void launchMagnificationSettings() {
+        final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
+                MAGNIFICATION_COMPONENT_NAME.flattenToShortString());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(
+                mContext.getDisplayId()).toBundle();
+        mContext.startActivityAsUser(intent, bundle, UserHandle.of(mUserId));
+        mContext.getSystemService(StatusBarManager.class).collapsePanels();
+    }
+
+    private void dismissNotification() {
+        unregisterReceiverIfNeeded();
+        mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+    }
+
+    private void unregisterReceiverIfNeeded() {
+        if (mNotificationActionReceiver == null) {
+            return;
+        }
+        mContext.unregisterReceiver(mNotificationActionReceiver);
+        mNotificationActionReceiver = null;
+    }
+
+    private class NotificationActionReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (TextUtils.isEmpty(action)) return;
+
+            mNeedToShowNotification = false;
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId);
+
+            if (ACTION_TURN_ON_IN_SETTINGS.equals(action)) {
+                launchMagnificationSettings();
+                dismissNotification();
+            } else if (ACTION_DISMISS.equals(action)) {
+                dismissNotification();
+            }
+        }
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4bde31f..069a5ea 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -61,6 +61,7 @@
 
 java_library_static {
     name: "services.core.unboosted",
+    defaults: ["platform_service_defaults"],
     srcs: [
         ":services.core.protologsrc",
         ":dumpstate_aidl",
@@ -74,6 +75,7 @@
         ":platform-compat-config",
         ":display-device-config",
         ":cec-config",
+        ":device-state-config",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
@@ -146,7 +148,6 @@
 
 java_library {
     name: "services.core",
-    defaults: ["platform_service_defaults"],
     static_libs: ["services.core.priorityboosted"],
 }
 
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9455051..bb4bbd5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -437,6 +437,10 @@
                 info.legacy.legacy.batteryChargeCounter);
         Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent",
                 info.legacy.legacy.batteryCurrent);
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "PlugType",
+                plugType(info.legacy.legacy));
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryStatus",
+                info.legacy.legacy.batteryStatus);
 
         synchronized (mLock) {
             if (!mUpdatesStopped) {
@@ -471,6 +475,18 @@
         dst.batteryTechnology = src.batteryTechnology;
     }
 
+    private static int plugType(HealthInfo healthInfo) {
+        if (healthInfo.chargerAcOnline) {
+            return BatteryManager.BATTERY_PLUGGED_AC;
+        } else if (healthInfo.chargerUsbOnline) {
+            return BatteryManager.BATTERY_PLUGGED_USB;
+        } else if (healthInfo.chargerWirelessOnline) {
+            return BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        } else {
+            return BATTERY_PLUGGED_NONE;
+        }
+    }
+
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
@@ -478,15 +494,7 @@
         mBatteryLevelCritical =
             mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
             && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
-        if (mHealthInfo.chargerAcOnline) {
-            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
-        } else if (mHealthInfo.chargerUsbOnline) {
-            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
-        } else if (mHealthInfo.chargerWirelessOnline) {
-            mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
-        } else {
-            mPlugType = BATTERY_PLUGGED_NONE;
-        }
+        mPlugType = plugType(mHealthInfo);
 
         if (DEBUG) {
             Slog.d(TAG, "Processing new values: "
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d586f00..ff0434f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -137,7 +137,6 @@
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.os.BasicShellCommandHandler;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -190,6 +189,7 @@
 import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BasicShellCommandHandler;
 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import com.android.server.am.BatteryStatsService;
@@ -5854,7 +5854,6 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
-    // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
     @NonNull
     private final NetworkRequest mDefaultRequest;
     // The NetworkAgentInfo currently satisfying the default request, if any.
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index d4e912b..8ed23f9 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -44,6 +44,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -145,16 +148,44 @@
     private long mLastPowerDown;
     private int mPowerButtonConsecutiveTaps;
     private int mPowerButtonSlowConsecutiveTaps;
+    private final UiEventLogger mUiEventLogger;
 
+    @VisibleForTesting
+    public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The user lifted the device just the right way to launch the camera.")
+        GESTURE_CAMERA_LIFT(658),
+
+        @UiEvent(doc = "The user wiggled the device just the right way to launch the camera.")
+        GESTURE_CAMERA_WIGGLE(659),
+
+        @UiEvent(doc = "The user double-tapped power quickly enough to launch the camera.")
+        GESTURE_CAMERA_DOUBLE_TAP_POWER(660),
+
+        @UiEvent(doc = "The user multi-tapped power quickly enough to signal an emergency.")
+        GESTURE_PANIC_TAP_POWER(661);
+
+        private final int mId;
+
+        GestureLauncherEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
     public GestureLauncherService(Context context) {
-        this(context, new MetricsLogger());
+        this(context, new MetricsLogger(), new UiEventLoggerImpl());
     }
 
     @VisibleForTesting
-    GestureLauncherService(Context context, MetricsLogger metricsLogger) {
+    GestureLauncherService(Context context, MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger) {
         super(context);
         mContext = context;
         mMetricsLogger = metricsLogger;
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
@@ -460,11 +491,12 @@
             if (launchCamera) {
                 mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
                         (int) powerTapInterval);
+                mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
             }
         } else if (launchEmergencyGesture) {
             Slog.i(TAG, "Emergency gesture detected, launching.");
             launchEmergencyGesture = handleEmergencyGesture();
-            // TODO(b/160006048): Add logging
+            mUiEventLogger.log(GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
         }
         mMetricsLogger.histogram("power_consecutive_short_tap_count",
                 mPowerButtonSlowConsecutiveTaps);
@@ -587,6 +619,7 @@
                 if (handleCameraGesture(true /* useWakelock */,
                         StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
                     mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
+                    mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_WIGGLE);
                     trackCameraLaunchEvent(event);
                 }
                 return;
@@ -671,6 +704,7 @@
                     if (handleCameraGesture(true /* useWakelock */,
                             StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
                         MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
+                        mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_LIFT);
                     }
                 } else {
                     if (DBG_CAMERA_LIFT) Slog.d(TAG, "Ignoring lift event");
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 5556e48..9d60b84 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.Build;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -31,6 +32,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.ClassLoaderFactory;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService.TargetUser;
 import com.android.server.am.EventLogTags;
@@ -121,7 +123,10 @@
         if (pathClassLoader == null) {
             // NB: the parent class loader should always be the system server class loader.
             // Changing it has implications that require discussion with the mainline team.
-            pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+            pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader(
+                    path, null /* librarySearchPath */, null /* libraryPermittedPath */,
+                    this.getClass().getClassLoader(), Build.VERSION.SDK_INT,
+                    true /* isNamespaceShared */, null /* classLoaderName */);
             mLoadedPaths.put(path, pathClassLoader);
         }
         final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ba52aee..eb55512 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -80,7 +80,6 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Pair;
 
@@ -102,13 +101,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Since phone process can be restarted, this class provides a centralized place
@@ -148,14 +144,14 @@
         int callerUid;
         int callerPid;
 
-        Set<Integer> eventList;
+        long events;
 
         int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
         int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 
-        boolean matchPhoneStateListenerEvent(int event) {
-            return (callback != null) && (this.eventList.contains(event));
+        boolean matchPhoneStateListenerEvent(long events) {
+            return (callback != null) && ((events & this.events) != 0);
         }
 
         boolean matchOnSubscriptionsChangedListener() {
@@ -183,7 +179,7 @@
                     + onSubscriptionsChangedListenerCallback
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
                     + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
-                    + " phoneId=" + phoneId + " events=" + eventList + "}";
+                    + " phoneId=" + phoneId + " events=" + Long.toHexString(events) + "}";
         }
     }
 
@@ -314,10 +310,6 @@
 
     private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
 
-    private boolean mIsDataEnabled = false;
-
-    private int mDataEnabledReason;
-
     /**
      * Per-phone map of precise data connection state. The key of the map is the pair of transport
      * type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -327,62 +319,40 @@
     private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
             mPreciseDataConnectionStates;
 
-    private static final Set<Integer> REQUIRE_PRECISE_PHONE_STATE_PERMISSION;
-    static {
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION = new HashSet<Integer>();
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_DATA_CONNECTION_REAL_TIME_INFO_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_REGISTRATION_FAILURE);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
-                PhoneStateListener.EVENT_DATA_ENABLED_CHANGED);
-    }
+    // Starting in Q, almost all cellular location requires FINE location enforcement.
+    // Prior to Q, cellular was available with COARSE location enforcement. Bits in this
+    // list will be checked for COARSE on apps targeting P or earlier and FINE on Q or later.
+    static final long ENFORCE_LOCATION_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_CELL_LOCATION
+                    | PhoneStateListener.LISTEN_CELL_INFO
+                    | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
+                    | PhoneStateListener.LISTEN_BARRING_INFO;
 
-    private boolean isLocationPermissionRequired(Set<Integer> events) {
-        return events.contains(PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_CELL_INFO_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_REGISTRATION_FAILURE)
-                || events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED);
-    }
+    static final long ENFORCE_PHONE_STATE_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
+                    | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+                    | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+                    | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
 
-    private boolean isPhoneStatePermissionRequired(Set<Integer> events) {
-        return events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
-    }
+    static final long ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_PRECISE_CALL_STATE
+                    | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
+                    | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
+                    | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED
+                    | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES
+                    | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
+                    | PhoneStateListener.LISTEN_BARRING_INFO
+                    | PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION;
 
-    private boolean isPrecisePhoneStatePermissionRequired(Set<Integer> events) {
-        for (Integer requireEvent : REQUIRE_PRECISE_PHONE_STATE_PERMISSION) {
-            if (events.contains(requireEvent)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    static final long READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
+                    | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS;
 
-    private boolean isActiveEmergencySessionPermissionRequired(Set<Integer> events) {
-        return events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL)
-                || events.contains(PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS);
-    }
-
-    private boolean isPrivilegedPhoneStatePermissionRequired(Set<Integer> events) {
-        return events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
-                || events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED);
-    }
+    static final long READ_PRIVILEGED_PHONE_STATE_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT
+                    | PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED
+                    | PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED
+                    | PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE;
 
     private static final int MSG_USER_SWITCHED = 1;
     private static final int MSG_UPDATE_DEFAULT_SUB = 2;
@@ -700,7 +670,7 @@
             r.callingFeatureId = callingFeatureId;
             r.callerUid = Binder.getCallingUid();
             r.callerPid = Binder.getCallingPid();
-            r.eventList = new ArraySet<>();
+            r.events = 0;
             if (DBG) {
                 log("listen oscl:  Register r=" + r);
             }
@@ -754,7 +724,7 @@
             r.callingFeatureId = callingFeatureId;
             r.callerUid = Binder.getCallingUid();
             r.callerPid = Binder.getCallingPid();
-            r.eventList = new ArraySet<>();
+            r.events = 0;
             if (DBG) {
                 log("listen ooscl:  Register r=" + r);
             }
@@ -827,338 +797,331 @@
         }
     }
 
+    @Deprecated
     @Override
-    public void listenWithEventList(int subId, String callingPackage, String callingFeatureId,
-            IPhoneStateListener callback, int[] events, boolean notifyNow) {
-        Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
-        listen(callingPackage, callingFeatureId, callback, eventList, notifyNow, subId);
+    public void listen(String callingPackage, IPhoneStateListener callback, int events,
+            boolean notifyNow) {
+        listenWithFeature(callingPackage, null, callback, events, notifyNow);
+    }
+
+    @Override
+    public void listenWithFeature(String callingPackage, String callingFeatureId,
+            IPhoneStateListener callback, long events, boolean notifyNow) {
+        listenForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, callingPackage,
+                callingFeatureId, callback, events, notifyNow);
+    }
+
+    @Override
+    public void listenForSubscriber(int subId, String callingPackage, String callingFeatureId,
+            IPhoneStateListener callback, long events, boolean notifyNow) {
+        listen(callingPackage, callingFeatureId, callback, events, notifyNow, subId);
     }
 
     private void listen(String callingPackage, @Nullable String callingFeatureId,
-            IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
+            IPhoneStateListener callback, long events, boolean notifyNow, int subId) {
         int callerUserId = UserHandle.getCallingUserId();
-
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
-                + " events=" + events + " notifyNow=" + notifyNow
-                + " subId=" + subId + " myUserId=" + UserHandle.myUserId()
-                + " callerUserId=" + callerUserId;
+                + " events=0x" + Long.toHexString(events) + " notifyNow=" + notifyNow + " subId="
+                + subId + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId;
         mListenLog.log(str);
         if (VDBG) {
             log(str);
         }
 
-        if (events.isEmpty()) {
-            if (DBG) {
-                log("listen: Unregister");
-            }
-            events.clear();
-            remove(callback.asBinder());
-            return;
-        }
-
-        // Checks permission and throws SecurityException for disallowed operations. For pre-M
-        // apps whose runtime permission has been revoked, we return immediately to skip sending
-        // events to the app without crashing it.
-        if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId,
-                "listen")) {
-            return;
-        }
-
-        int phoneId = getPhoneIdFromSubId(subId);
-        synchronized (mRecords) {
-            // register
-            IBinder b = callback.asBinder();
-            boolean doesLimitApply =
-                    Binder.getCallingUid() != Process.SYSTEM_UID
-                            && Binder.getCallingUid() != Process.PHONE_UID
-                            && Binder.getCallingUid() != Process.myUid();
-            Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
-
-            if (r == null) {
+        if (events != PhoneStateListener.LISTEN_NONE) {
+            // Checks permission and throws SecurityException for disallowed operations. For pre-M
+            // apps whose runtime permission has been revoked, we return immediately to skip sending
+            // events to the app without crashing it.
+            if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId,
+                    "listen")) {
                 return;
             }
 
-            r.context = mContext;
-            r.callback = callback;
-            r.callingPackage = callingPackage;
-            r.callingFeatureId = callingFeatureId;
-            r.callerUid = Binder.getCallingUid();
-            r.callerPid = Binder.getCallingPid();
-            // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
-            // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
-            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-                r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-            } else {//APP specify subID
-                r.subId = subId;
-            }
-            r.phoneId = phoneId;
-            r.eventList = events;
-            if (DBG) {
-                log("listen:  Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
-            }
-            if (notifyNow && validatePhoneId(phoneId)) {
-                if (events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED)) {
-                    try {
-                        if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
-                        ServiceState rawSs = new ServiceState(mServiceState[phoneId]);
-                        if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
-                            r.callback.onServiceStateChanged(rawSs);
-                        } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
-                            r.callback.onServiceStateChanged(
-                                    rawSs.createLocationInfoSanitizedCopy(false));
-                        } else {
-                            r.callback.onServiceStateChanged(
-                                    rawSs.createLocationInfoSanitizedCopy(true));
+            int phoneId = getPhoneIdFromSubId(subId);
+            synchronized (mRecords) {
+                // register
+                IBinder b = callback.asBinder();
+                boolean doesLimitApply =
+                        Binder.getCallingUid() != Process.SYSTEM_UID
+                        && Binder.getCallingUid() != Process.PHONE_UID
+                        && Binder.getCallingUid() != Process.myUid();
+                Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+                if (r == null) {
+                    return;
+                }
+
+                r.context = mContext;
+                r.callback = callback;
+                r.callingPackage = callingPackage;
+                r.callingFeatureId = callingFeatureId;
+                r.callerUid = Binder.getCallingUid();
+                r.callerPid = Binder.getCallingPid();
+                // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+                // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                    r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                 } else {//APP specify subID
+                    r.subId = subId;
+                }
+                r.phoneId = phoneId;
+                r.events = events;
+                if (DBG) {
+                    log("listen:  Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
+                }
+                if (notifyNow && validatePhoneId(phoneId)) {
+                    if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
+                        try {
+                            if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
+                            ServiceState rawSs = new ServiceState(mServiceState[phoneId]);
+                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                                r.callback.onServiceStateChanged(rawSs);
+                            } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
+                                r.callback.onServiceStateChanged(
+                                        rawSs.createLocationInfoSanitizedCopy(false));
+                            } else {
+                                r.callback.onServiceStateChanged(
+                                        rawSs.createLocationInfoSanitizedCopy(true));
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) {
-                    try {
-                        if (mSignalStrength[phoneId] != null) {
-                            int gsmSignalStrength = mSignalStrength[phoneId]
-                                    .getGsmSignalStrength();
-                            r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
-                                    : gsmSignalStrength));
+                    if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
+                        try {
+                            if (mSignalStrength[phoneId] != null) {
+                                int gsmSignalStrength = mSignalStrength[phoneId]
+                                        .getGsmSignalStrength();
+                                r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+                                        : gsmSignalStrength));
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) {
-                    try {
-                        r.callback.onMessageWaitingIndicatorChanged(
-                                mMessageWaiting[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) {
-                    try {
-                        r.callback.onCallForwardingIndicatorChanged(
-                                mCallForwarding[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (validateEventAndUserLocked(
-                        r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) {
-                    try {
-                        if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
-                        if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
-                                && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
-                            // null will be translated to empty CellLocation object in client.
-                            r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
+                    if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
+                        try {
+                            r.callback.onMessageWaitingIndicatorChanged(
+                                    mMessageWaiting[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_CALL_STATE_CHANGED)) {
-                    try {
-                        r.callback.onCallStateChanged(mCallState[phoneId],
-                                getCallIncomingNumber(r, phoneId));
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
+                        try {
+                            r.callback.onCallForwardingIndicatorChanged(
+                                    mCallForwarding[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) {
-                    try {
-                        r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
+                        try {
+                            if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
+                            if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                                // null will be translated to empty CellLocation object in client.
+                                r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
+                        try {
+                            r.callback.onCallStateChanged(mCallState[phoneId],
+                                     getCallIncomingNumber(r, phoneId));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+                        try {
+                            r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
                                 mDataConnectionNetworkType[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED)) {
-                    try {
-                        r.callback.onDataActivity(mDataActivity[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)) {
-                    try {
-                        if (mSignalStrength[phoneId] != null) {
-                            r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
-                    updateReportSignalStrengthDecision(r.subId);
-                    try {
-                        if (mSignalStrength[phoneId] != null) {
-                            r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+                    if ((events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
+                        try {
+                            r.callback.onDataActivity(mDataActivity[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (validateEventAndUserLocked(
-                        r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) {
-                    try {
-                        if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
-                                + mCellInfo.get(phoneId));
-                        if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
-                                && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
-                            r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+                    if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+                        try {
+                            if (mSignalStrength[phoneId] != null) {
+                                r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED)) {
-                    try {
-                        r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) {
-                    try {
-                        r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
-                                mCallPreciseDisconnectCause[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) {
-                    try {
-                        r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId));
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) {
-                    try {
-                        for (PreciseDataConnectionState pdcs
-                                : mPreciseDataConnectionStates.get(phoneId).values()) {
-                            r.callback.onPreciseDataConnectionStateChanged(pdcs);
+                    if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
+                            != 0) {
+                        updateReportSignalStrengthDecision(r.subId);
+                        try {
+                            if (mSignalStrength[phoneId] != null) {
+                                r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED)) {
-                    try {
-                        r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) {
-                    try {
-                        r.callback.onVoiceActivationStateChanged(
-                                mVoiceActivationState[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED)) {
-                    try {
-                        r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) {
-                    try {
-                        r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
-                    }
-                }
-                if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) {
-                    try {
-                        if (mTelephonyDisplayInfos[phoneId] != null) {
-                            r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]);
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
+                        try {
+                            if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
+                                    + mCellInfo.get(phoneId));
+                            if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                                r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
                         }
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)) {
-                    try {
-                        r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
+                        try {
+                            r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) {
-                    try {
-                        r.callback.onPhoneCapabilityChanged(mPhoneCapability);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
+                        try {
+                            r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
+                                    mCallPreciseDisconnectCause[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) {
-                    try {
-                        r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) {
+                        try {
+                            r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED)) {
-                    try {
-                        r.callback.onRadioPowerStateChanged(mRadioPowerState);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
+                        try {
+                            for (PreciseDataConnectionState pdcs
+                                    : mPreciseDataConnectionStates.get(phoneId).values()) {
+                                r.callback.onPreciseDataConnectionStateChanged(pdcs);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)) {
-                    try {
-                        r.callback.onSrvccStateChanged(mSrvccState[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
+                        try {
+                            r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)) {
-                    try {
-                        r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
+                        try {
+                            r.callback.onVoiceActivationStateChanged(
+                                    mVoiceActivationState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(PhoneStateListener.EVENT_BARRING_INFO_CHANGED)) {
-                    BarringInfo barringInfo = mBarringInfo.get(phoneId);
-                    BarringInfo biNoLocation = barringInfo != null
-                            ? barringInfo.createLocationInfoSanitizedCopy() : null;
-                    if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
-                    try {
-                        r.callback.onBarringInfoChanged(
-                                checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
-                                        ? barringInfo : biNoLocation);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) !=0) {
+                        try {
+                            r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) {
-                    try {
-                        r.callback.onPhysicalChannelConfigChanged(
-                                mPhysicalChannelConfigs);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+                        try {
+                            r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
-                }
-                if (events.contains(
-                        PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) {
-                    try {
-                        r.callback.onDataEnabledChanged(mIsDataEnabled, mDataEnabledReason);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+                        try {
+                            if (mTelephonyDisplayInfos[phoneId] != null) {
+                                r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
+                        try {
+                            r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) {
+                        try {
+                            r.callback.onPhoneCapabilityChanged(mPhoneCapability);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener
+                            .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
+                        try {
+                            r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+                        try {
+                            r.callback.onRadioPowerStateChanged(mRadioPowerState);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) != 0) {
+                        try {
+                            r.callback.onSrvccStateChanged(mSrvccState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
+                        try {
+                            r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_BARRING_INFO) != 0) {
+                        BarringInfo barringInfo = mBarringInfo.get(phoneId);
+                        BarringInfo biNoLocation = barringInfo != null
+                                ? barringInfo.createLocationInfoSanitizedCopy() : null;
+                        if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+                        try {
+                            r.callback.onBarringInfoChanged(
+                                    checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
+                                            ? barringInfo : biNoLocation);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION) != 0) {
+                        try {
+                            r.callback.onPhysicalChannelConfigurationChanged(
+                                    mPhysicalChannelConfigs);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                 }
             }
+        } else {
+            if(DBG) log("listen: Unregister");
+            remove(callback.asBinder());
         }
     }
 
@@ -1170,7 +1133,7 @@
                 // If any of the system clients wants to always listen to signal strength,
                 // we need to set it on.
                 if (r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+                        PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
                     telephonyManager.createForSubscriptionId(subscriptionId)
                             .setAlwaysReportSignalStrength(true);
                     return;
@@ -1271,7 +1234,7 @@
                     // strength is removed from registry records, we need to check if
                     // the signal strength decision needs to update on its slot.
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+                            PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
                         updateReportSignalStrengthDecision(r.subId);
                     }
                     return;
@@ -1291,8 +1254,8 @@
 
         synchronized (mRecords) {
             for (Record r : mRecords) {
-                if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED)
-                        && (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
+                if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
+                        (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
                     try {
                         // Ensure the listener has read call log permission; if they do not return
                         // an empty phone number.
@@ -1326,9 +1289,9 @@
                 mCallState[phoneId] = state;
                 mCallIncomingNumber[phoneId] = incomingNumber;
                 for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.EVENT_CALL_STATE_CHANGED)
-                            && (r.subId == subId)
-                            && (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
+                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
+                            (r.subId == subId) &&
+                            (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
                         try {
                             String incomingNumberOrEmpty = getCallIncomingNumber(r, phoneId);
                             r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
@@ -1368,9 +1331,8 @@
                         log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
                                 + " phoneId=" + phoneId + " state=" + state);
                     }
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_SERVICE_STATE_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) &&
+                            idMatch(r.subId, subId, phoneId)) {
 
                         try {
                             ServiceState stateToSend;
@@ -1431,7 +1393,7 @@
                     try {
                         if ((activationType == SIM_ACTIVATION_TYPE_VOICE)
                                 && r.matchPhoneStateListenerEvent(
-                                        PhoneStateListener.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
+                                        PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE)
                                 && idMatch(r.subId, subId, phoneId)) {
                             if (DBG) {
                                 log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r
@@ -1442,7 +1404,7 @@
                         }
                         if ((activationType == SIM_ACTIVATION_TYPE_DATA)
                                 && r.matchPhoneStateListenerEvent(
-                                        PhoneStateListener.EVENT_DATA_ACTIVATION_STATE_CHANGED)
+                                        PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE)
                                 && idMatch(r.subId, subId, phoneId)) {
                             if (DBG) {
                                 log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r
@@ -1481,11 +1443,9 @@
                         log("notifySignalStrengthForPhoneId: r=" + r + " subId=" + subId
                                 + " phoneId=" + phoneId + " ss=" + signalStrength);
                     }
-                    if ((r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)
+                    if ((r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)
                             || r.matchPhoneStateListenerEvent(
-                                    PhoneStateListener.
-                                            EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))
+                                    PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH))
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             if (DBG) {
@@ -1498,9 +1458,8 @@
                             mRemoveList.add(r.binder);
                         }
                     }
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTH) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             int gsmSignalStrength = signalStrength.getGsmSignalStrength();
                             int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength);
@@ -1546,8 +1505,8 @@
                 }
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_CARRIER_NETWORK_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onCarrierNetworkChange(active);
                         } catch (RemoteException ex) {
@@ -1577,10 +1536,9 @@
             if (validatePhoneId(phoneId)) {
                 mCellInfo.set(phoneId, cellInfo);
                 for (Record r : mRecords) {
-                    if (validateEventAndUserLocked(
-                            r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)
-                            && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&
+                            idMatch(r.subId, subId, phoneId) &&
+                            (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
                                     && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
                         try {
                             if (DBG_LOC) {
@@ -1612,8 +1570,8 @@
                 mMessageWaiting[phoneId] = mwi;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onMessageWaitingIndicatorChanged(mwi);
                         } catch (RemoteException ex) {
@@ -1639,8 +1597,8 @@
                 mUserMobileDataState[phoneId] = state;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onUserMobileDataStateChanged(state);
                         } catch (RemoteException ex) {
@@ -1678,7 +1636,7 @@
                 mTelephonyDisplayInfos[phoneId] = telephonyDisplayInfo;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)
+                            PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
                             && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
                         try {
                             r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
@@ -1710,8 +1668,8 @@
                 mCallForwarding[phoneId] = cfi;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onCallForwardingIndicatorChanged(cfi);
                         } catch (RemoteException ex) {
@@ -1738,9 +1696,8 @@
                 mDataActivity[phoneId] = state;
                 for (Record r : mRecords) {
                     // Notify by correct subId.
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_DATA_ACTIVITY_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_DATA_ACTIVITY) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onDataActivity(state);
                         } catch (RemoteException ex) {
@@ -1787,7 +1744,7 @@
                     mLocalLog.log(str);
                     for (Record r : mRecords) {
                         if (r.matchPhoneStateListenerEvent(
-                                PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)
+                                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE)
                                 && idMatch(r.subId, subId, phoneId)) {
                             try {
                                 if (DBG) {
@@ -1812,7 +1769,7 @@
                 if (!Objects.equals(oldState, preciseState)) {
                     for (Record r : mRecords) {
                         if (r.matchPhoneStateListenerEvent(
-                                PhoneStateListener.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)
+                                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
                                 && idMatch(r.subId, subId, phoneId)) {
                             try {
                                 r.callback.onPreciseDataConnectionStateChanged(preciseState);
@@ -1857,10 +1814,9 @@
             if (validatePhoneId(phoneId) && !Objects.equals(cellIdentity, mCellIdentity[phoneId])) {
                 mCellIdentity[phoneId] = cellIdentity;
                 for (Record r : mRecords) {
-                    if (validateEventAndUserLocked(
-                            r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)
-                            && (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
+                            idMatch(r.subId, subId, phoneId) &&
+                            (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
                                     && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
                         try {
                             if (DBG_LOC) {
@@ -1911,8 +1867,7 @@
                 }
 
                 for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_PRECISE_CALL_STATE_CHANGED)
+                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
@@ -1921,7 +1876,7 @@
                         }
                     }
                     if (notifyCallAttributes && r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)
+                            PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
@@ -1970,7 +1925,7 @@
                 mImsReasonInfo.set(phoneId, imsReasonInfo);
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)
+                            PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             if (DBG_LOC) {
@@ -2002,8 +1957,8 @@
                 mSrvccState[phoneId] = state;
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_SRVCC_STATE_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) &&
+                            idMatch(r.subId, subId, phoneId)) {
                         try {
                             if (DBG_LOC) {
                                 log("notifySrvccStateChanged: mSrvccState=" + state + " r=" + r);
@@ -2031,7 +1986,7 @@
                         log("notifyOemHookRawEventForSubscriber:  r=" + r + " subId=" + subId);
                     }
                     if ((r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_OEM_HOOK_RAW))
+                            PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT))
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onOemHookRawEvent(rawData);
@@ -2059,7 +2014,7 @@
 
             for (Record r : mRecords) {
                 if (r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.EVENT_PHONE_CAPABILITY_CHANGED)) {
+                        PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE)) {
                     try {
                         r.callback.onPhoneCapabilityChanged(capability);
                     } catch (RemoteException ex) {
@@ -2084,7 +2039,7 @@
         synchronized (mRecords) {
             for (Record r : mRecords) {
                 if (r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED)) {
+                        PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) {
                     try {
                         r.callback.onActiveDataSubIdChanged(activeDataSubId);
                     } catch (RemoteException ex) {
@@ -2111,7 +2066,7 @@
 
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_RADIO_POWER_STATE_CHANGED)
+                            PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onRadioPowerStateChanged(state);
@@ -2140,7 +2095,7 @@
 
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
+                            PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -2172,7 +2127,7 @@
             for (Record r : mRecords) {
                 // Send to all listeners regardless of subscription
                 if (r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.EVENT_OUTGOING_EMERGENCY_CALL)) {
+                        PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL)) {
                     try {
                         r.callback.onOutgoingEmergencyCall(emergencyNumber, subId);
                     } catch (RemoteException ex) {
@@ -2196,7 +2151,7 @@
                 for (Record r : mRecords) {
                     // Send to all listeners regardless of subscription
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_OUTGOING_EMERGENCY_SMS)) {
+                            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)) {
                         try {
                             r.callback.onOutgoingEmergencySms(emergencyNumber, subId);
                         } catch (RemoteException ex) {
@@ -2226,7 +2181,7 @@
 
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_CALL_ATTRIBUTES_CHANGED)
+                            PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
@@ -2257,7 +2212,7 @@
             if (validatePhoneId(phoneId)) {
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_REGISTRATION_FAILURE)
+                            PhoneStateListener.LISTEN_REGISTRATION_FAILURE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onRegistrationFailed(
@@ -2300,7 +2255,7 @@
                 if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_BARRING_INFO_CHANGED)
+                            PhoneStateListener.LISTEN_BARRING_INFO)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             if (DBG_LOC) {
@@ -2327,14 +2282,14 @@
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
+    public void notifyPhysicalChannelConfigurationForSubscriber(
             int subId, List<PhysicalChannelConfig> configs) {
-        if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) {
+        if (!checkNotifyPermission("notifyPhysicalChannelConfiguration()")) {
             return;
         }
 
         if (VDBG) {
-            log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs);
+            log("notifyPhysicalChannelConfiguration: subId=" + subId + " configs=" + configs);
         }
 
         synchronized (mRecords) {
@@ -2343,15 +2298,15 @@
                 mPhysicalChannelConfigs.set(phoneId, configs.get(phoneId));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)
+                            PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             if (DBG_LOC) {
-                                log("notifyPhysicalChannelConfig: "
+                                log("notifyPhysicalChannelConfiguration: "
                                         + "mPhysicalChannelConfigs="
                                         + configs + " r=" + r);
                             }
-                            r.callback.onPhysicalChannelConfigChanged(configs);
+                            r.callback.onPhysicalChannelConfigurationChanged(configs);
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -2661,18 +2616,18 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
-    private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
-                                            @Nullable String callingFeatureId, String message) {
+    private boolean checkListenerPermission(long events, int subId, String callingPackage,
+            @Nullable String callingFeatureId, String message) {
         LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
                 new LocationAccessPolicy.LocationPermissionQuery.Builder()
-                        .setCallingPackage(callingPackage)
-                        .setMethod(message + " events: " + events)
-                        .setCallingPid(Binder.getCallingPid())
-                        .setCallingUid(Binder.getCallingUid());
+                .setCallingPackage(callingPackage)
+                .setMethod(message + " events: " + events)
+                .setCallingPid(Binder.getCallingPid())
+                .setCallingUid(Binder.getCallingUid());
 
         boolean shouldCheckLocationPermissions = false;
 
-        if (isLocationPermissionRequired(events)) {
+        if ((events & ENFORCE_LOCATION_PERMISSION_MASK) != 0) {
             // Everything that requires fine location started in Q. So far...
             locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
             // If we're enforcing fine starting in Q, we also want to enforce coarse even for
@@ -2697,14 +2652,14 @@
             }
         }
 
-        if (isPhoneStatePermissionRequired(events)) {
+        if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                     mContext, subId, callingPackage, callingFeatureId, message)) {
                 isPermissionCheckSuccessful = false;
             }
         }
 
-        if (isPrecisePhoneStatePermissionRequired(events)) {
+        if ((events & ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
             // check if calling app has either permission READ_PRECISE_PHONE_STATE
             // or with carrier privileges
             try {
@@ -2715,20 +2670,21 @@
             }
         }
 
-        if (isActiveEmergencySessionPermissionRequired(events)) {
+        if ((events & READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
         }
 
-        if ((events.contains(PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))) {
+        if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH, null);
         }
 
-        if (isPrivilegedPhoneStatePermissionRequired(events)) {
+        if ((events & READ_PRIVILEGED_PHONE_STATE_PERMISSION_MASK) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
         }
+
         return isPermissionCheckSuccessful;
     }
 
@@ -2736,25 +2692,25 @@
         int size = mRemoveList.size();
         if (VDBG) log("handleRemoveListLocked: mRemoveList.size()=" + size);
         if (size > 0) {
-            for (IBinder b : mRemoveList) {
+            for (IBinder b: mRemoveList) {
                 remove(b);
             }
             mRemoveList.clear();
         }
     }
 
-    private boolean validateEventAndUserLocked(Record r, int event) {
+    private boolean validateEventsAndUserLocked(Record r, int events) {
         int foregroundUser;
         final long callingIdentity = Binder.clearCallingIdentity();
         boolean valid = false;
         try {
             foregroundUser = ActivityManager.getCurrentUser();
             valid = UserHandle.getUserId(r.callerUid) == foregroundUser
-                    && r.matchPhoneStateListenerEvent(event);
+                    && r.matchPhoneStateListenerEvent(events);
             if (DBG | DBG_LOC) {
-                log("validateEventAndUserLocked: valid=" + valid
+                log("validateEventsAndUserLocked: valid=" + valid
                         + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
-                        + " r.eventList=" + r.eventList + " event=" + event);
+                        + " r.events=" + r.events + " events=" + events);
             }
         } finally {
             Binder.restoreCallingIdentity(callingIdentity);
@@ -2866,14 +2822,9 @@
     }
 
     private void checkPossibleMissNotify(Record r, int phoneId) {
-        Set<Integer> events = r.eventList;
+        long events = r.events;
 
-        if (events == null || events.isEmpty()) {
-            log("checkPossibleMissNotify: events = null.");
-            return;
-        }
-
-        if ((events.contains(PhoneStateListener.EVENT_SERVICE_STATE_CHANGED))) {
+        if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
             try {
                 if (VDBG) log("checkPossibleMissNotify: onServiceStateChanged state=" +
                         mServiceState[phoneId]);
@@ -2892,9 +2843,8 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTHS_CHANGED)
-                || events.contains(
-                PhoneStateListener.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0
+                || (events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
             try {
                 if (mSignalStrength[phoneId] != null) {
                     SignalStrength signalStrength = mSignalStrength[phoneId];
@@ -2909,7 +2859,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_SIGNAL_STRENGTH_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
             try {
                 if (mSignalStrength[phoneId] != null) {
                     int gsmSignalStrength = mSignalStrength[phoneId]
@@ -2926,7 +2876,7 @@
             }
         }
 
-        if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_INFO_CHANGED)) {
+        if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
             try {
                 if (DBG_LOC) {
                     log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "
@@ -2941,7 +2891,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
             try {
                 if (VDBG) {
                     log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId="
@@ -2953,7 +2903,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_DISPLAY_INFO_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
             try {
                 if (VDBG) {
                     log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
@@ -2967,7 +2917,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
             try {
                 if (VDBG) {
                     log("checkPossibleMissNotify: onMessageWaitingIndicatorChanged phoneId="
@@ -2980,7 +2930,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
             try {
                 if (VDBG) {
                     log("checkPossibleMissNotify: onCallForwardingIndicatorChanged phoneId="
@@ -2993,7 +2943,7 @@
             }
         }
 
-        if (validateEventAndUserLocked(r, PhoneStateListener.EVENT_CELL_LOCATION_CHANGED)) {
+        if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
             try {
                 if (DBG_LOC) {
                     log("checkPossibleMissNotify: onCellLocationChanged mCellIdentity = "
@@ -3009,7 +2959,7 @@
             }
         }
 
-        if (events.contains(PhoneStateListener.EVENT_DATA_CONNECTION_STATE_CHANGED)) {
+        if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
             try {
                 if (DBG) {
                     log("checkPossibleMissNotify: onDataConnectionStateChanged(mDataConnectionState"
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f376473..165b6a1 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -165,9 +165,13 @@
         // TODO: Clear VCN configuration, trigger teardown as necessary
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    class VcnNetworkProvider extends NetworkProvider {
-        VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
+    /**
+     * Network provider for VCN networks.
+     *
+     * @hide
+     */
+    public class VcnNetworkProvider extends NetworkProvider {
+        VcnNetworkProvider(Context context, Looper looper) {
             super(context, looper, VcnNetworkProvider.class.getSimpleName());
         }
 
diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java
index 7691852..d7e95df 100644
--- a/services/core/java/com/android/server/adb/AdbShellCommand.java
+++ b/services/core/java/com/android/server/adb/AdbShellCommand.java
@@ -16,7 +16,7 @@
 
 package com.android.server.adb;
 
-import android.os.BasicShellCommandHandler;
+import com.android.modules.utils.BasicShellCommandHandler;
 
 import java.io.PrintWriter;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 01e77f8..d6f7299 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1630,6 +1630,8 @@
         }
     }
 
+    // TODO: remove as part of fixing b/173627642
+    @SuppressWarnings("AndroidFrameworkCompatChange")
     private void postFgsNotificationLocked(ServiceRecord r) {
         boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled;
         if (!showNow) {
diff --git a/services/core/java/com/android/server/am/ActiveUids.java b/services/core/java/com/android/server/am/ActiveUids.java
index c86c29f..27be53a 100644
--- a/services/core/java/com/android/server/am/ActiveUids.java
+++ b/services/core/java/com/android/server/am/ActiveUids.java
@@ -52,9 +52,8 @@
 
     void clear() {
         mActiveUids.clear();
-        if (mPostChangesToAtm) {
-            mService.mAtmInternal.onActiveUidsCleared();
-        }
+        // It is only called for a temporal container with mPostChangesToAtm == false or test case.
+        // So there is no need to notify activity task manager.
     }
 
     UidRecord get(int uid) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c3ba150..ed2faf9 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -519,16 +519,24 @@
         return data;
     }
 
-    public ParcelFileDescriptor getStatisticsStream() {
+    /**
+     * Returns parceled BatteryStats as a MemoryFile.
+     *
+     * @param forceUpdate If true, runs a sync to get fresh battery stats. Otherwise,
+     *                  returns the current values.
+     */
+    public ParcelFileDescriptor getStatisticsStream(boolean forceUpdate) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        // Drain the handler queue to make sure we've handled all pending works, so we'll get
-        // an accurate stats.
-        awaitCompletion();
-        syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+        if (forceUpdate) {
+            // Drain the handler queue to make sure we've handled all pending works, so we'll get
+            // an accurate stats.
+            awaitCompletion();
+            syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+        }
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 09937e3..da47040 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -94,7 +94,6 @@
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.SyncNotedAppOp;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -3018,28 +3017,6 @@
         }
     }
 
-    private boolean isTrustedVoiceServiceProxy(String packageName, int code) {
-        if (code != OP_RECORD_AUDIO) {
-            return false;
-        }
-        final String voiceRecognitionComponent = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
-        final String voiceInteractionComponent = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
-
-        final String voiceRecognitionServicePackageName =
-                getComponentPackageNameFromString(voiceRecognitionComponent);
-        final String voiceInteractionServicePackageName =
-                getComponentPackageNameFromString(voiceInteractionComponent);
-        return Objects.equals(packageName, voiceRecognitionServicePackageName) && Objects.equals(
-                voiceRecognitionServicePackageName, voiceInteractionServicePackageName);
-    }
-
-    private String getComponentPackageNameFromString(String from) {
-        ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null;
-        return componentName != null ? componentName.getPackageName() : "";
-    }
-
     @Override
     public int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
             String proxiedAttributionTag, int proxyUid, String proxyPackageName,
@@ -3057,10 +3034,12 @@
 
         // This is a workaround for R QPR, new API change is not allowed. We only allow the current
         // voice recognizer is also the voice interactor to noteproxy op.
-        final boolean isTrustVoiceServiceProxy = isTrustedVoiceServiceProxy(proxyPackageName, code);
+        final boolean isTrustVoiceServiceProxy =
+                AppOpsManager.isTrustedVoiceServiceProxy(mContext, proxyPackageName, code);
+        final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
         final boolean isProxyTrusted = mContext.checkPermission(
                 Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
-                == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy;
+                == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
 
         final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
@@ -3524,10 +3503,12 @@
 
         // This is a workaround for R QPR, new API change is not allowed. We only allow the current
         // voice recognizer is also the voice interactor to noteproxy op.
-        final boolean isTrustVoiceServiceProxy = isTrustedVoiceServiceProxy(proxyPackageName, code);
+        final boolean isTrustVoiceServiceProxy =
+                AppOpsManager.isTrustedVoiceServiceProxy(mContext, proxyPackageName, code);
+        final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
         final boolean isProxyTrusted = mContext.checkPermission(
                 Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
-                == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy;
+                == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
 
         final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 84de25c..72d3835 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -48,10 +48,10 @@
             ]
         },
         {
-            "name": "CtsStatsdHostTestCases",
+            "name": "CtsStatsdAtomHostTestCases",
             "options": [
                 {
-                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testAppOps"
+                    "include-filter": "android.cts.statsdatom.appops.AppOpsTests#testAppOps"
                 }
             ]
         }        
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java
new file mode 100644
index 0000000..77e6ceb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlNativeHandleUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * A utility class for the AIDL implementation of NativeHandle - {@link
+ * android.hardware.common.NativeHandle}.
+ */
+public final class AidlNativeHandleUtils {
+
+    /**
+     * Converts a {@link android.os.NativeHandle} to a {@link android.hardware.common.NativeHandle}
+     * by duplicating the underlying file descriptors.
+     *
+     * Both the original and new handle must be closed after use.
+     *
+     * @param handle {@link android.os.NativeHandle}. Can be null.
+     * @return a {@link android.hardware.common.NativeHandle} representation of {@code handle}.
+     * Returns null if {@code handle} is null.
+     * @throws IOException if any of the underlying calls to {@code dup} fail.
+     */
+    public static @Nullable android.hardware.common.NativeHandle dup(
+            @Nullable android.os.NativeHandle handle) throws IOException {
+        if (handle == null) {
+            return null;
+        }
+        android.hardware.common.NativeHandle res = new android.hardware.common.NativeHandle();
+        final FileDescriptor[] fds = handle.getFileDescriptors();
+        res.ints = handle.getInts().clone();
+        res.fds = new ParcelFileDescriptor[fds.length];
+        for (int i = 0; i < fds.length; ++i) {
+            res.fds[i] = ParcelFileDescriptor.dup(fds[i]);
+        }
+        return res;
+    }
+
+    /**
+     * Closes the file descriptors contained within a {@link android.hardware.common.NativeHandle}.
+     * This is a no-op if the handle is null.
+     *
+     * This should only be used for handles that own their file descriptors, for example handles
+     * obtained using {@link #dup(android.os.NativeHandle)}.
+     *
+     * @param handle {@link android.hardware.common.NativeHandle}. Can be null.
+     * @throws IOException if any of the underlying calls to {@code close} fail.
+     */
+    public static void close(@Nullable android.hardware.common.NativeHandle handle)
+            throws IOException {
+        if (handle != null) {
+            for (ParcelFileDescriptor fd : handle.fds) {
+                if (fd != null) {
+                    fd.close();
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f950862..f09df1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -39,6 +39,7 @@
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -51,6 +52,7 @@
     @NonNull private final int[] mEnrollIgnoreList;
     @NonNull private final int[] mEnrollIgnoreListVendor;
     @Nullable private ICancellationSignal mCancellationSignal;
+    @Nullable private android.hardware.common.NativeHandle mPreviewSurface;
     private final int mMaxTemplatesPerUser;
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@@ -66,6 +68,24 @@
         mEnrollIgnoreListVendor = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
+        try {
+            // We must manually close the duplicate handle after it's no longer needed.
+            // The caller is responsible for closing the original handle.
+            mPreviewSurface = AidlNativeHandleUtils.dup(previewSurface);
+        } catch (IOException e) {
+            mPreviewSurface = null;
+            Slog.e(TAG, "Failed to dup previewSurface", e);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        try {
+            AidlNativeHandleUtils.close(mPreviewSurface);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to close mPreviewSurface", e);
+        }
+        super.destroy();
     }
 
     @Override
@@ -94,10 +114,9 @@
 
         try {
             // TODO(b/172593978): Pass features.
-            // TODO(b/172593521): Pass mPreviewSurface as android.hardware.common.NativeHandle.
             mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
                     HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
-                    null /* mPreviewSurface */);
+                    mPreviewSurface);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 26f6264..36796b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -205,7 +205,7 @@
 
     private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
             int userId) throws RemoteException {
-        // Note that per IFingerprint createSession contract, this method will block until all
+        // Note that per IFace createSession contract, this method will block until all
         // existing operations are canceled/finished. However, also note that this is fine, since
         // this method "withoutHandler" means it should only ever be invoked from the worker thread,
         // so callers will never be blocked.
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 3555bbe..c57ab50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -71,7 +71,6 @@
 import com.android.server.biometrics.sensors.face.LockoutHalImpl;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -259,8 +258,7 @@
 
                 if (!removed.isEmpty()) {
                     // Convert to old fingerprint-like behavior, where remove() receives
-                    // one removal
-                    // at a time. This way, remove can share some more common code.
+                    // one removal at a time. This way, remove can share some more common code.
                     for (int i = 0; i < removed.size(); i++) {
                         final int id = removed.get(i);
                         final Face face = new Face("", id, deviceId);
@@ -290,21 +288,16 @@
                 final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
 
                 if (!faceIds.isEmpty()) {
-                    // Convert to old fingerprint-like behavior, where enumerate()
-                    // receives one
-                    // template at a time. This way, enumerate can share some more common
-                    // code.
+                    // Convert to old fingerprint-like behavior, where enumerate() receives one
+                    // template at a time. This way, enumerate can share some more common code.
                     for (int i = 0; i < faceIds.size(); i++) {
                         final Face face = new Face("", faceIds.get(i), deviceId);
                         enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
                     }
                 } else {
-                    // For face, the HIDL contract is to receive an empty list when there
-                    // are no
-                    // templates enrolled. Send a null identifier since we don't consume
-                    // them
-                    // anywhere, and send remaining == 0 so this code can be shared with
-                    // Fingerprint@2.1
+                    // For face, the HIDL contract is to receive an empty list when there are no
+                    // templates enrolled. Send a null identifier since we don't consume them
+                    // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
                     enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
                 }
             });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 437ecd7..fd1181b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
@@ -89,7 +88,8 @@
                     HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
-            onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+                    0 /* vendorCode */);
             mCallback.onClientFinished(this, false /* success */);
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c750b90..2a89a88 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -65,7 +65,7 @@
         final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
                 .size();
         if (enrolled >= limit) {
-            Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+            Slog.w(TAG, "Too many fingerprints registered, user: " + getTargetUserId());
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index a4ae9c8..2a81426 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hardware.CameraSessionStats;
+import android.hardware.CameraStreamStats;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceProxy;
 import android.media.AudioManager;
@@ -36,11 +38,13 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserManager;
+import android.stats.camera.nano.CameraStreamProto;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.framework.protobuf.nano.MessageNano;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
@@ -97,7 +101,10 @@
     @interface DeviceStateFlags {}
 
     // Maximum entries to keep in usage history before dumping out
-    private static final int MAX_USAGE_HISTORY = 100;
+    private static final int MAX_USAGE_HISTORY = 20;
+    // Number of stream statistics being dumped for each camera session
+    // Must be equal to number of CameraStreamProto in CameraActionEvent
+    private static final int MAX_STREAM_STATISTICS = 5;
 
     private final Context mContext;
     private final ServiceThread mHandlerThread;
@@ -123,7 +130,6 @@
     private final ArrayMap<String, CameraUsageEvent> mActiveCameraUsage = new ArrayMap<>();
     private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
 
-    private final MetricsLogger mLogger = new MetricsLogger();
     private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
     private static final String NFC_SERVICE_BINDER_NAME = "nfc";
     private static final IBinder nfcInterfaceToken = new Binder();
@@ -137,27 +143,50 @@
      * Structure to track camera usage
      */
     private static class CameraUsageEvent {
+        public final String mCameraId;
         public final int mCameraFacing;
         public final String mClientName;
         public final int mAPILevel;
+        public final boolean mIsNdk;
+        public final int mAction;
+        public final int mLatencyMs;
+        public final int mOperatingMode;
 
         private boolean mCompleted;
+        public int mInternalReconfigure;
+        public long mRequestCount;
+        public long mResultErrorCount;
+        public boolean mDeviceError;
+        public List<CameraStreamStats> mStreamStats;
         private long mDurationOrStartTimeMs;  // Either start time, or duration once completed
 
-        public CameraUsageEvent(int facing, String clientName, int apiLevel) {
+        CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel,
+                boolean isNdk, int action, int latencyMs, int operatingMode) {
+            mCameraId = cameraId;
             mCameraFacing = facing;
             mClientName = clientName;
             mAPILevel = apiLevel;
             mDurationOrStartTimeMs = SystemClock.elapsedRealtime();
             mCompleted = false;
+            mIsNdk = isNdk;
+            mAction = action;
+            mLatencyMs = latencyMs;
+            mOperatingMode = operatingMode;
         }
 
-        public void markCompleted() {
+        public void markCompleted(int internalReconfigure, long requestCount,
+                long resultErrorCount, boolean deviceError,
+                List<CameraStreamStats>  streamStats) {
             if (mCompleted) {
                 return;
             }
             mCompleted = true;
             mDurationOrStartTimeMs = SystemClock.elapsedRealtime() - mDurationOrStartTimeMs;
+            mInternalReconfigure = internalReconfigure;
+            mRequestCount = requestCount;
+            mResultErrorCount = resultErrorCount;
+            mDeviceError = deviceError;
+            mStreamStats = streamStats;
             if (CameraServiceProxy.DEBUG) {
                 Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
                         " was in use by " + mClientName + " for " +
@@ -211,19 +240,22 @@
         }
 
         @Override
-        public void notifyCameraState(String cameraId, int newCameraState, int facing,
-                String clientName, int apiLevel) {
+        public void notifyCameraState(CameraSessionStats cameraState) {
             if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
                 Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
                         " camera service UID!");
                 return;
             }
-            String state = cameraStateToString(newCameraState);
-            String facingStr = cameraFacingToString(facing);
-            if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " facing " + facingStr + " state now " +
-                    state + " for client " + clientName + " API Level " + apiLevel);
+            String state = cameraStateToString(cameraState.getNewCameraState());
+            String facingStr = cameraFacingToString(cameraState.getFacing());
+            if (DEBUG) {
+                Slog.v(TAG, "Camera " + cameraState.getCameraId()
+                        + " facing " + facingStr + " state now " + state
+                        + " for client " + cameraState.getClientName()
+                        + " API Level " + cameraState.getApiLevel());
+            }
 
-            updateActivityCount(cameraId, newCameraState, facing, clientName, apiLevel);
+            updateActivityCount(cameraState);
         }
     };
 
@@ -385,20 +417,80 @@
         private void logCameraUsageEvent(CameraUsageEvent e) {
             int facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__UNKNOWN;
             switch(e.mCameraFacing) {
-                case ICameraServiceProxy.CAMERA_FACING_BACK:
+                case CameraSessionStats.CAMERA_FACING_BACK:
                     facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__BACK;
                     break;
-                case ICameraServiceProxy.CAMERA_FACING_FRONT:
+                case CameraSessionStats.CAMERA_FACING_FRONT:
                     facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__FRONT;
                     break;
-                case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
+                case CameraSessionStats.CAMERA_FACING_EXTERNAL:
                     facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__EXTERNAL;
                     break;
                 default:
                     Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing);
             }
+
+            int streamCount = 0;
+            if (e.mStreamStats != null) {
+                streamCount = e.mStreamStats.size();
+            }
+            if (CameraServiceProxy.DEBUG) {
+                Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
+                        + " clientName " + e.mClientName
+                        + ", duration " + e.getDuration()
+                        + ", APILevel " + e.mAPILevel
+                        + ", cameraId " + e.mCameraId
+                        + ", facing " + facing
+                        + ", isNdk " + e.mIsNdk
+                        + ", latencyMs " + e.mLatencyMs
+                        + ", operatingMode " + e.mOperatingMode
+                        + ", internalReconfigure " + e.mInternalReconfigure
+                        + ", requestCount " + e.mRequestCount
+                        + ", resultErrorCount " + e.mResultErrorCount
+                        + ", deviceError " + e.mDeviceError
+                        + ", streamCount is " + streamCount);
+            }
+            // Convert from CameraStreamStats to CameraStreamProto
+            CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
+            for (int i = 0; i < MAX_STREAM_STATISTICS; i++) {
+                streamProtos[i] = new CameraStreamProto();
+                if (i < streamCount) {
+                    CameraStreamStats streamStats = e.mStreamStats.get(i);
+                    streamProtos[i].width = streamStats.getWidth();
+                    streamProtos[i].height = streamStats.getHeight();
+                    streamProtos[i].format = streamStats.getFormat();
+                    streamProtos[i].dataSpace = streamStats.getDataSpace();
+                    streamProtos[i].usage = streamStats.getUsage();
+                    streamProtos[i].requestCount = streamStats.getRequestCount();
+                    streamProtos[i].errorCount = streamStats.getErrorCount();
+                    streamProtos[i].firstCaptureLatencyMillis = streamStats.getStartLatencyMs();
+                    streamProtos[i].maxHalBuffers = streamStats.getMaxHalBuffers();
+                    streamProtos[i].maxAppBuffers = streamStats.getMaxAppBuffers();
+
+                    if (CameraServiceProxy.DEBUG) {
+                        Slog.v(TAG, "Stream " + i + ": width " + streamProtos[i].width
+                                + ", height " + streamProtos[i].height
+                                + ", format " + streamProtos[i].format
+                                + ", dataSpace " + streamProtos[i].dataSpace
+                                + ", usage " + streamProtos[i].usage
+                                + ", requestCount " + streamProtos[i].requestCount
+                                + ", errorCount " + streamProtos[i].errorCount
+                                + ", firstCaptureLatencyMillis "
+                                + streamProtos[i].firstCaptureLatencyMillis
+                                + ", maxHalBuffers " + streamProtos[i].maxHalBuffers
+                                + ", maxAppBuffers " + streamProtos[i].maxAppBuffers);
+                    }
+                }
+            }
             FrameworkStatsLog.write(FrameworkStatsLog.CAMERA_ACTION_EVENT, e.getDuration(),
-                    e.mAPILevel, e.mClientName, facing);
+                    e.mAPILevel, e.mClientName, facing, e.mCameraId, e.mAction, e.mIsNdk,
+                    e.mLatencyMs, e.mOperatingMode, e.mInternalReconfigure,
+                    e.mRequestCount, e.mResultErrorCount, e.mDeviceError,
+                    streamCount, MessageNano.toByteArray(streamProtos[0]),
+                    MessageNano.toByteArray(streamProtos[1]),
+                    MessageNano.toByteArray(streamProtos[2]),
+                    MessageNano.toByteArray(streamProtos[3]),
+                    MessageNano.toByteArray(streamProtos[4]));
         }
     }
 
@@ -410,35 +502,6 @@
         synchronized(mLock) {
             // Randomize order of events so that it's not meaningful
             Collections.shuffle(mCameraUsageHistory);
-            for (CameraUsageEvent e : mCameraUsageHistory) {
-                if (DEBUG) {
-                    Slog.v(TAG, "Camera: " + e.mClientName + " used a camera facing " +
-                            cameraFacingToString(e.mCameraFacing) + " for " +
-                            e.getDuration() + " ms");
-                }
-                int subtype = 0;
-                switch(e.mCameraFacing) {
-                    case ICameraServiceProxy.CAMERA_FACING_BACK:
-                        subtype = MetricsEvent.CAMERA_BACK_USED;
-                        break;
-                    case ICameraServiceProxy.CAMERA_FACING_FRONT:
-                        subtype = MetricsEvent.CAMERA_FRONT_USED;
-                        break;
-                    case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
-                        subtype = MetricsEvent.CAMERA_EXTERNAL_USED;
-                        break;
-                    default:
-                        continue;
-                }
-                LogMaker l = new LogMaker(MetricsEvent.ACTION_CAMERA_EVENT)
-                        .setType(MetricsEvent.TYPE_ACTION)
-                        .setSubtype(subtype)
-                        .setLatency(e.getDuration())
-                        .addTaggedData(MetricsEvent.FIELD_CAMERA_API_LEVEL, e.mAPILevel)
-                        .setPackageName(e.mClientName);
-                mLogger.write(l);
-            }
-
             mLogWriterService.execute(new EventWriterTask(
                         new ArrayList<CameraUsageEvent>(mCameraUsageHistory)));
 
@@ -569,13 +632,25 @@
         return true;
     }
 
-    private void updateActivityCount(String cameraId, int newCameraState, int facing,
-            String clientName, int apiLevel) {
+    private void updateActivityCount(CameraSessionStats cameraState) {
+        String cameraId = cameraState.getCameraId();
+        int newCameraState = cameraState.getNewCameraState();
+        int facing = cameraState.getFacing();
+        String clientName = cameraState.getClientName();
+        int apiLevel = cameraState.getApiLevel();
+        boolean isNdk = cameraState.isNdk();
+        int sessionType = cameraState.getSessionType();
+        int internalReconfigureCount = cameraState.getInternalReconfigureCount();
+        int latencyMs = cameraState.getLatencyMs();
+        long requestCount = cameraState.getRequestCount();
+        long resultErrorCount = cameraState.getResultErrorCount();
+        boolean deviceError = cameraState.getDeviceErrorFlag();
+        List<CameraStreamStats> streamStats = cameraState.getStreamStats();
         synchronized(mLock) {
             // Update active camera list and notify NFC if necessary
             boolean wasEmpty = mActiveCameraUsage.isEmpty();
             switch (newCameraState) {
-                case ICameraServiceProxy.CAMERA_STATE_OPEN:
+                case CameraSessionStats.CAMERA_STATE_OPEN:
                     // Notify the audio subsystem about the facing of the most-recently opened
                     // camera This can be used to select the best audio tuning in case video
                     // recording with that camera will happen.  Since only open events are used, if
@@ -584,13 +659,18 @@
                     AudioManager audioManager = getContext().getSystemService(AudioManager.class);
                     if (audioManager != null) {
                         // Map external to front for audio tuning purposes
-                        String facingStr = (facing == ICameraServiceProxy.CAMERA_FACING_BACK) ?
+                        String facingStr = (facing == CameraSessionStats.CAMERA_FACING_BACK) ?
                                 "back" : "front";
                         String facingParameter = "cameraFacing=" + facingStr;
                         audioManager.setParameters(facingParameter);
                     }
+                    CameraUsageEvent openEvent = new CameraUsageEvent(
+                            cameraId, facing, clientName, apiLevel, isNdk,
+                            FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__OPEN,
+                            latencyMs, sessionType);
+                    mCameraUsageHistory.add(openEvent);
                     break;
-                case ICameraServiceProxy.CAMERA_STATE_ACTIVE:
+                case CameraSessionStats.CAMERA_STATE_ACTIVE:
                     // Check current active camera IDs to see if this package is already talking to
                     // some camera
                     boolean alreadyActivePackage = false;
@@ -609,40 +689,55 @@
                     }
 
                     // Update activity events
-                    CameraUsageEvent newEvent = new CameraUsageEvent(facing, clientName, apiLevel);
+                    CameraUsageEvent newEvent = new CameraUsageEvent(
+                            cameraId, facing, clientName, apiLevel, isNdk,
+                            FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__SESSION,
+                            latencyMs, sessionType);
                     CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
                     if (oldEvent != null) {
                         Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
-                        oldEvent.markCompleted();
+                        oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
+                                /*resultErrorCount*/0, /*deviceError*/false, streamStats);
                         mCameraUsageHistory.add(oldEvent);
                     }
                     break;
-                case ICameraServiceProxy.CAMERA_STATE_IDLE:
-                case ICameraServiceProxy.CAMERA_STATE_CLOSED:
+                case CameraSessionStats.CAMERA_STATE_IDLE:
+                case CameraSessionStats.CAMERA_STATE_CLOSED:
                     CameraUsageEvent doneEvent = mActiveCameraUsage.remove(cameraId);
-                    if (doneEvent == null) break;
+                    if (doneEvent != null) {
 
-                    doneEvent.markCompleted();
-                    mCameraUsageHistory.add(doneEvent);
-                    if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) {
-                        dumpUsageEvents();
-                    }
+                        doneEvent.markCompleted(internalReconfigureCount, requestCount,
+                                resultErrorCount, deviceError, streamStats);
+                        mCameraUsageHistory.add(doneEvent);
 
-                    // Check current active camera IDs to see if this package is still talking to
-                    // some camera
-                    boolean stillActivePackage = false;
-                    for (int i = 0; i < mActiveCameraUsage.size(); i++) {
-                        if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) {
-                            stillActivePackage = true;
-                            break;
+                        // Check current active camera IDs to see if this package is still
+                        // talking to some camera
+                        boolean stillActivePackage = false;
+                        for (int i = 0; i < mActiveCameraUsage.size(); i++) {
+                            if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) {
+                                stillActivePackage = true;
+                                break;
+                            }
+                        }
+                        // If not longer active, notify window manager about this package being done
+                        // with camera
+                        if (!stillActivePackage) {
+                            WindowManagerInternal wmi =
+                                    LocalServices.getService(WindowManagerInternal.class);
+                            wmi.removeNonHighRefreshRatePackage(clientName);
                         }
                     }
-                    // If not longer active, notify window manager about this package being done
-                    // with camera
-                    if (!stillActivePackage) {
-                        WindowManagerInternal wmi =
-                                LocalServices.getService(WindowManagerInternal.class);
-                        wmi.removeNonHighRefreshRatePackage(clientName);
+
+                    if (newCameraState == CameraSessionStats.CAMERA_STATE_CLOSED) {
+                        CameraUsageEvent closeEvent = new CameraUsageEvent(
+                                cameraId, facing, clientName, apiLevel, isNdk,
+                                FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__CLOSE,
+                                latencyMs, sessionType);
+                        mCameraUsageHistory.add(closeEvent);
+                    }
+
+                    if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) {
+                        dumpUsageEvents();
                     }
 
                     break;
@@ -683,10 +778,10 @@
 
     private static String cameraStateToString(int newCameraState) {
         switch (newCameraState) {
-            case ICameraServiceProxy.CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN";
-            case ICameraServiceProxy.CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE";
-            case ICameraServiceProxy.CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE";
-            case ICameraServiceProxy.CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED";
+            case CameraSessionStats.CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN";
+            case CameraSessionStats.CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE";
+            case CameraSessionStats.CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE";
+            case CameraSessionStats.CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED";
             default: break;
         }
         return "CAMERA_STATE_UNKNOWN";
@@ -694,9 +789,9 @@
 
     private static String cameraFacingToString(int cameraFacing) {
         switch (cameraFacing) {
-            case ICameraServiceProxy.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK";
-            case ICameraServiceProxy.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT";
-            case ICameraServiceProxy.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL";
+            case CameraSessionStats.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK";
+            case CameraSessionStats.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT";
+            case CameraSessionStats.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL";
             default: break;
         }
         return "CAMERA_FACING_UNKNOWN";
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4d959d0..bdd315d 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -207,7 +207,8 @@
     @VisibleForTesting protected String mPackage;
     private int mOwnerUID;
     private boolean mIsPackageTargetingAtLeastQ;
-    private String mInterface;
+    @VisibleForTesting
+    protected String mInterface;
     private Connection mConnection;
 
     /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 3172a04..d7dcbde 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -100,7 +100,7 @@
     private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
 
     public DeviceStateManagerService(@NonNull Context context) {
-        this(context, new DeviceStatePolicyImpl());
+        this(context, new DeviceStatePolicyImpl(context));
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index f2413ed..2ba8758 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -22,10 +22,22 @@
 /**
  * Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
  * operations.
+ * @hide
  */
 public class DisplayGroup {
 
-    final List<LogicalDisplay> mDisplays = new ArrayList<>();
+    public static final int DEFAULT = 0;
+
+    private final List<LogicalDisplay> mDisplays = new ArrayList<>();
+    private final int mGroupId;
+
+    DisplayGroup(int groupId) {
+        mGroupId = groupId;
+    }
+
+    int getGroupId() {
+        return mGroupId;
+    }
 
     void addDisplay(LogicalDisplay display) {
         if (!mDisplays.contains(display)) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 004e481..4e60f1f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2536,6 +2536,13 @@
         }
 
         @Override
+        public int getDisplayGroupId(int displayId) {
+            synchronized (mSyncRoot) {
+                return mLogicalDisplayMapper.getDisplayGroupIdLocked(displayId);
+            }
+        }
+
+        @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 45c38b4..6b74170 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -94,6 +94,7 @@
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
     private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+    private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1;
 
     /** A mapping from logical display id to display group. */
     private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
@@ -178,6 +179,15 @@
         }
     }
 
+    public int getDisplayGroupIdLocked(int displayId) {
+        final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+        if (displayGroup != null) {
+            return displayGroup.getGroupId();
+        }
+
+        return -1;
+    }
+
     public void dumpLocked(PrintWriter pw) {
         pw.println("LogicalDisplayMapper:");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
@@ -309,7 +319,8 @@
 
         final DisplayGroup displayGroup;
         if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
-            displayGroup = new DisplayGroup();
+            final int groupId = assignDisplayGroupIdLocked(isDefault);
+            displayGroup = new DisplayGroup(groupId);
         } else {
             displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
         }
@@ -345,7 +356,8 @@
                 if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
                     // The display should have its own DisplayGroup.
                     if (defaultDisplayGroup.removeDisplay(display)) {
-                        final DisplayGroup displayGroup = new DisplayGroup();
+                        final int groupId = assignDisplayGroupIdLocked(false);
+                        final DisplayGroup displayGroup = new DisplayGroup(groupId);
                         displayGroup.addDisplay(display);
                         mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
                     }
@@ -381,6 +393,10 @@
         return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
     }
 
+    private int assignDisplayGroupIdLocked(boolean isDefault) {
+        return isDefault ? DisplayGroup.DEFAULT : mNextNonDefaultGroupId++;
+    }
+
     private int assignLayerStackLocked(int displayId) {
         // Currently layer stacks and display ids are the same.
         // This need not be the case.
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index d0cc8e71..42dcbeb 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -501,15 +501,6 @@
     static final int DISABLED = 0;
     static final int ENABLED = 1;
 
-    @IntDef({
-            VERSION_1_4,
-            VERSION_2_0
-    })
-    @interface CecVersion {}
-    static final int VERSION_1_3 = 0x04;
-    static final int VERSION_1_4 = 0x05;
-    static final int VERSION_2_0 = 0x06;
-
     static final int ALL_DEVICE_TYPES_TV = 7;
     static final int ALL_DEVICE_TYPES_RECORDER = 6;
     static final int ALL_DEVICE_TYPES_TUNER = 5;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index e7f302c..fb71d95 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.input.InputManager;
@@ -560,7 +561,7 @@
     protected abstract List<Integer> getDeviceFeatures();
 
     protected boolean handleGiveFeatures(HdmiCecMessage message) {
-        if (mService.getCecVersion() < Constants.VERSION_2_0) {
+        if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 109f6a7..9ca1f91 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -128,25 +128,6 @@
                 String.valueOf(addr));
     }
 
-    @ServiceThreadOnly
-    void queryDisplayStatus(IHdmiControlCallback callback) {
-        assertRunOnServiceThread();
-        List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
-        if (!actions.isEmpty()) {
-            Slog.i(TAG, "queryDisplayStatus already in progress");
-            actions.get(0).addCallback(callback);
-            return;
-        }
-        DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
-                callback);
-        if (action == null) {
-            Slog.w(TAG, "Cannot initiate queryDisplayStatus");
-            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
-            return;
-        }
-        addAndStartAction(action);
-    }
-
     @Override
     @ServiceThreadOnly
     void onHotplug(int portId, boolean connected) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 62b7d8f..3b3ac74 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -70,6 +70,25 @@
         super(service, deviceType);
     }
 
+    @ServiceThreadOnly
+    void queryDisplayStatus(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
+        if (!actions.isEmpty()) {
+            Slog.i(TAG, "queryDisplayStatus already in progress");
+            actions.get(0).addCallback(callback);
+            return;
+        }
+        DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
+                callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate queryDisplayStatus");
+            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
+    }
+
     @Override
     @ServiceThreadOnly
     void onHotplug(int portId, boolean connected) {
@@ -87,10 +106,14 @@
     @ServiceThreadOnly
     protected void sendStandby(int deviceId) {
         assertRunOnServiceThread();
-
-        // Send standby to TV only for now
-        int targetAddress = Constants.ADDR_TV;
-        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+        String sendStandbyOnSleep = mService.getHdmiCecConfig().getStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP);
+        if (sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST)) {
+            mService.sendCecCommand(
+                    HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
+            return;
+        }
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
     }
 
     @ServiceThreadOnly
@@ -113,6 +136,44 @@
     }
 
     @ServiceThreadOnly
+    void toggleAndFollowTvPower() {
+        assertRunOnServiceThread();
+        // Wake up Android framework to take over CEC control from the microprocessor.
+        mService.wakeUp();
+        mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int status) {
+                if (status == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+                    Slog.i(TAG, "TV power toggle: TV power status unknown");
+                    sendUserControlPressedAndReleased(Constants.ADDR_TV,
+                            HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+                    // Source device remains awake.
+                } else if (status == HdmiControlManager.POWER_STATUS_ON
+                        || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
+                    Slog.i(TAG, "TV power toggle: turning off TV");
+                    sendStandby(0 /*unused */);
+                    // Source device goes to standby, to follow the toggled TV power state.
+                    mService.standby();
+                } else if (status == HdmiControlManager.POWER_STATUS_STANDBY
+                        || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+                    Slog.i(TAG, "TV power toggle: turning on TV");
+                    oneTouchPlay(new IHdmiControlCallback.Stub() {
+                        @Override
+                        public void onComplete(int result) {
+                            if (result != HdmiControlManager.RESULT_SUCCESS) {
+                                Slog.w(TAG, "Failed to complete One Touch Play. result=" + result);
+                                sendUserControlPressedAndReleased(Constants.ADDR_TV,
+                                        HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+                            }
+                        }
+                    });
+                    // Source device remains awake, to follow the toggled TV power state.
+                }
+            }
+        });
+    }
+
+    @ServiceThreadOnly
     protected void onActiveSourceLost() {
         // Nothing to do.
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 1a481b6..96303ce 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -16,10 +16,10 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 
 import com.android.server.hdmi.Constants.AudioCodec;
-import com.android.server.hdmi.Constants.CecVersion;
 
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
@@ -696,9 +696,9 @@
         return buildCommand(src, dest, Constants.MESSAGE_GIVE_FEATURES);
     }
 
-    static HdmiCecMessage buildReportFeatures(int src, @CecVersion int cecVersion,
-            List<Integer> allDeviceTypes, int rcProfile, List<Integer> rcFeatures,
-            List<Integer> deviceFeatures) {
+    static HdmiCecMessage buildReportFeatures(int src,
+            @HdmiControlManager.HdmiCecVersion int cecVersion, List<Integer> allDeviceTypes,
+            int rcProfile, List<Integer> rcFeatures, List<Integer> deviceFeatures) {
         byte cecVersionByte = (byte) (cecVersion & 0xFF);
         byte deviceTypes = 0;
         for (Integer deviceType : allDeviceTypes) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index baed9cc..d2a2d72 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -201,7 +201,7 @@
         addValidationInfo(
                 Constants.MESSAGE_REPORT_POWER_STATUS,
                 new OneByteRangeValidator(0x00, 0x03),
-                DEST_DIRECT);
+                DEST_DIRECT | DEST_BROADCAST);
 
         // Messages for the General Protocol.
         addValidationInfo(Constants.MESSAGE_FEATURE_ABORT,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
new file mode 100644
index 0000000..c4dadaa
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import android.hardware.hdmi.HdmiControlManager;
+
+/**
+ * Storage of HDMI-CEC power status and controls possible cases where power status changes must also
+ * broadcast {@code <Report Power Status>} messages.
+ *
+ * All HDMI-CEC related power status changes should be done through this class.
+ */
+class HdmiCecPowerStatusController {
+
+    private final HdmiControlService mHdmiControlService;
+
+    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+
+    HdmiCecPowerStatusController(HdmiControlService hdmiControlService) {
+        mHdmiControlService = hdmiControlService;
+    }
+
+    int getPowerStatus() {
+        return mPowerStatus;
+    }
+
+    boolean isPowerStatusOn() {
+        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON;
+    }
+
+    boolean isPowerStatusStandby() {
+        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
+    }
+
+    boolean isPowerStatusTransientToOn() {
+        return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+    }
+
+    boolean isPowerStatusTransientToStandby() {
+        return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+    }
+
+    @ServiceThreadOnly
+    void setPowerStatus(int powerStatus) {
+        setPowerStatus(powerStatus, true);
+    }
+
+    @ServiceThreadOnly
+    void setPowerStatus(int powerStatus, boolean sendPowerStatusUpdate) {
+        if (powerStatus == mPowerStatus) {
+            return;
+        }
+
+        mPowerStatus = powerStatus;
+        if (sendPowerStatusUpdate
+                && mHdmiControlService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            sendReportPowerStatus(mPowerStatus);
+        }
+    }
+
+    private void sendReportPowerStatus(int powerStatus) {
+        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllLocalDevices()) {
+            mHdmiControlService.sendCecCommand(
+                    HdmiCecMessageBuilder.buildReportPowerStatus(localDevice.mAddress,
+                            Constants.ADDR_BROADCAST, powerStatus));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dcd6c02..01ec3b4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -350,8 +350,8 @@
 
     private HdmiCecMessageValidator mMessageValidator;
 
-    @ServiceThreadOnly
-    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+    private final HdmiCecPowerStatusController mPowerStatusController =
+            new HdmiCecPowerStatusController(this);
 
     @ServiceThreadOnly
     private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -530,7 +530,8 @@
             mIoThread.start();
             mIoLooper = mIoThread.getLooper();
         }
-        mPowerStatus = getInitialPowerStatus();
+
+        mPowerStatusController.setPowerStatus(getInitialPowerStatus());
         mProhibitMode = false;
         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
         mHdmiCecVolumeControlEnabled = readBooleanSetting(
@@ -673,10 +674,12 @@
      * Updates the power status once the initialization of local devices is complete.
      */
     private void updatePowerStatusOnInitializeCecComplete() {
-        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
-            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        } else if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
-            mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+        if (mPowerStatusController.isPowerStatusTransientToOn()) {
+            mHandler.post(() -> mPowerStatusController.setPowerStatus(
+                    HdmiControlManager.POWER_STATUS_ON));
+        } else if (mPowerStatusController.isPowerStatusTransientToStandby()) {
+            mHandler.post(() -> mPowerStatusController.setPowerStatus(
+                    HdmiControlManager.POWER_STATUS_STANDBY));
         }
     }
 
@@ -1686,7 +1689,7 @@
         public void oneTouchPlay(final IHdmiControlCallback callback) {
             enforceAccessPermission();
             int pid = Binder.getCallingPid();
-            Slog.d(TAG, "Proccess pid: " + pid + " is calling oneTouchPlay.");
+            Slog.d(TAG, "Process pid: " + pid + " is calling oneTouchPlay.");
             runOnServiceThread(new Runnable() {
                 @Override
                 public void run() {
@@ -1696,6 +1699,19 @@
         }
 
         @Override
+        public void toggleAndFollowTvPower() {
+            enforceAccessPermission();
+            int pid = Binder.getCallingPid();
+            Slog.d(TAG, "Process pid: " + pid + " is calling toggleAndFollowTvPower.");
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.toggleAndFollowTvPower();
+                }
+            });
+        }
+
+        @Override
         public void queryDisplayStatus(final IHdmiControlCallback callback) {
             enforceAccessPermission();
             runOnServiceThread(new Runnable() {
@@ -2191,7 +2207,7 @@
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
             pw.println("mProhibitMode: " + mProhibitMode);
-            pw.println("mPowerStatus: " + mPowerStatus);
+            pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
 
             // System settings
             pw.println("System_settings:");
@@ -2362,7 +2378,23 @@
     }
 
     @ServiceThreadOnly
-    private void queryDisplayStatus(final IHdmiControlCallback callback) {
+    @VisibleForTesting
+    protected void toggleAndFollowTvPower() {
+        assertRunOnServiceThread();
+        HdmiCecLocalDeviceSource source = playback();
+        if (source == null) {
+            source = audioSystem();
+        }
+
+        if (source == null) {
+            Slog.w(TAG, "Local source device not available");
+            return;
+        }
+        source.toggleAndFollowTvPower();
+    }
+
+    @ServiceThreadOnly
+    protected void queryDisplayStatus(final IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         if (!mAddressAllocated) {
             mDisplayStatusCallback = callback;
@@ -2371,9 +2403,13 @@
             return;
         }
 
-        HdmiCecLocalDevicePlayback source = playback();
+        HdmiCecLocalDeviceSource source = playback();
         if (source == null) {
-            Slog.w(TAG, "Local playback device not available");
+            source = audioSystem();
+        }
+
+        if (source == null) {
+            Slog.w(TAG, "Local source device not available");
             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
             return;
         }
@@ -2831,34 +2867,34 @@
     @ServiceThreadOnly
     int getPowerStatus() {
         assertRunOnServiceThread();
-        return mPowerStatus;
+        return mPowerStatusController.getPowerStatus();
     }
 
     @ServiceThreadOnly
     @VisibleForTesting
     void setPowerStatus(int powerStatus) {
         assertRunOnServiceThread();
-        mPowerStatus = powerStatus;
+        mPowerStatusController.setPowerStatus(powerStatus);
     }
 
     @ServiceThreadOnly
     boolean isPowerOnOrTransient() {
         assertRunOnServiceThread();
-        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
-                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+        return mPowerStatusController.isPowerStatusOn()
+                || mPowerStatusController.isPowerStatusTransientToOn();
     }
 
     @ServiceThreadOnly
     boolean isPowerStandbyOrTransient() {
         assertRunOnServiceThread();
-        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
-                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+        return mPowerStatusController.isPowerStatusStandby()
+                || mPowerStatusController.isPowerStatusTransientToStandby();
     }
 
     @ServiceThreadOnly
     boolean isPowerStandby() {
         assertRunOnServiceThread();
-        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
+        return mPowerStatusController.isPowerStatusStandby();
     }
 
     @ServiceThreadOnly
@@ -2895,7 +2931,8 @@
     @ServiceThreadOnly
     private void onWakeUp(@WakeReason final int wakeUpAction) {
         assertRunOnServiceThread();
-        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
+                false);
         if (mCecController != null) {
             if (mHdmiControlEnabled) {
                 int startReason = -1;
@@ -2926,14 +2963,15 @@
     @VisibleForTesting
     protected void onStandby(final int standbyAction) {
         assertRunOnServiceThread();
-        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
+                false);
         invokeVendorCommandListenersOnControlStateChanged(false,
                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
 
         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
 
         if (!isStandbyMessageReceived() && !canGoToStandby()) {
-            mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
             for (HdmiCecLocalDevice device : devices) {
                 device.onStandby(mStandbyMessageReceived, standbyAction);
             }
@@ -3013,10 +3051,10 @@
         assertRunOnServiceThread();
         Slog.v(TAG, "onStandbyCompleted");
 
-        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
+        if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
             return;
         }
-        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
         for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
             device.onStandby(mStandbyMessageReceived, standbyAction);
         }
diff --git a/services/core/java/com/android/server/hdmi/cec_key_handling.md b/services/core/java/com/android/server/hdmi/cec_key_handling.md
index d150dd3..1b41a67 100644
--- a/services/core/java/com/android/server/hdmi/cec_key_handling.md
+++ b/services/core/java/com/android/server/hdmi/cec_key_handling.md
@@ -9,13 +9,13 @@
 
 The general action for key handling is described in the table
 
-| Android Key | TV Panel                             | OTT                                  | Soundbar                          |
-| ----------- | -----------------                    | -------------------                  | -------------------               |
-| general     | Send to active source                | handle on device                     | handle on device                  |
-| POWER       | Toggle the device power state        | Toggle the TV power state            | Toggle the TV power state         |
-| TV_POWER    | Toggle the device power state        | Toggle the TV power state            | Toggle the TV power state         |
-| HOME        | Turn on TV, Set active Source to TV, go to home screen | OTP, and go to home screen | OTP, and go to home screen |
-| volume keys | Handle on device or send to soundbar | Send to TV or soundbar               | Handle on device or send to TV    |
+| Android Key | TV Panel                                               | OTT                        | Soundbar                                               |
+| ----------- | -----------------                                      | -------------------        | -------------------                                    |
+| general     | Send to active source                                  | handle on device           | handle on device                                       |
+| POWER       | Toggle the device power state  | Toggle the OTT power state, TV power state follows | Toggle the soundbar power state, TV power state follows|
+| TV_POWER    | Toggle the device power state  | Toggle the TV power state, OTT power state follows | Toggle the TV power state, soundbar power state follows|
+| HOME        | Turn on TV, Set active Source to TV, go to home screen | OTP, and go to home screen | OTP, and go to home screen                             |
+| volume keys | Handle on device or send to soundbar                   | Send to TV or soundbar     | Handle on device or send to TV                         |
 
 Special cases and flags for each key are described below
 
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index 8c1afab..7d3ccbf 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -16,9 +16,10 @@
 
 package com.android.server.location;
 
-import android.os.BasicShellCommandHandler;
 import android.os.UserHandle;
 
+import com.android.modules.utils.BasicShellCommandHandler;
+
 import java.io.PrintWriter;
 import java.util.Objects;
 
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 4a85342..5a90fa7 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -322,12 +322,28 @@
 
     @Override
     protected boolean isActive(GeofenceRegistration registration) {
-        CallerIdentity identity = registration.getIdentity();
-        return registration.isPermitted()
-                && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
-                && mSettingsHelper.isLocationEnabled(identity.getUserId())
-                && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName());
+        return registration.isPermitted() && isActive(registration.getIdentity());
+    }
+
+    private boolean isActive(CallerIdentity identity) {
+        if (identity.isSystemServer()) {
+            if (!mSettingsHelper.isLocationEnabled(mUserInfoHelper.getCurrentUserId())) {
+                return false;
+            }
+        } else {
+            if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
+                return false;
+            }
+            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+                return false;
+            }
+            if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+                    identity.getPackageName())) {
+                return false;
+            }
+        }
+
+        return true;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index e68f595..7e848e0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -266,11 +266,30 @@
         CallerIdentity identity = registration.getIdentity();
         return registration.isPermitted()
                 && (registration.isForeground() || isBackgroundRestrictionExempt(identity))
-                && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
-                && mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
-                identity.getUserId())
-                && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName());
+                && isActive(identity);
+    }
+
+    private boolean isActive(CallerIdentity identity) {
+        if (identity.isSystemServer()) {
+            if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+                    mUserInfoHelper.getCurrentUserId())) {
+                return false;
+            }
+        } else {
+            if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+                    identity.getUserId())) {
+                return false;
+            }
+            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+                return false;
+            }
+            if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+                    identity.getPackageName())) {
+                return false;
+            }
+        }
+
+        return true;
     }
 
     private boolean isBackgroundRestrictionExempt(CallerIdentity identity) {
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index 56dd92a..d4a8fbd 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -88,11 +88,6 @@
         return mUserManager;
     }
 
-    /**
-     * Returns an array of running user ids. This will include all running users, and will also
-     * include any profiles of the running users. The caller must never mutate the returned
-     * array.
-     */
     @Override
     public int[] getRunningUserIds() {
         IActivityManager activityManager = getActivityManager();
@@ -110,10 +105,6 @@
         }
     }
 
-    /**
-     * Returns true if the given user id is either the current user or a profile of the current
-     * user.
-     */
     @Override
     public boolean isCurrentUserId(@UserIdInt int userId) {
         ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
@@ -130,6 +121,21 @@
     }
 
     @Override
+    public @UserIdInt int getCurrentUserId() {
+        ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
+        if (activityManagerInternal != null) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return activityManagerInternal.getCurrentUserId();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            return UserHandle.USER_NULL;
+        }
+    }
+
+    @Override
     protected int[] getProfileIds(@UserIdInt int userId) {
         UserManager userManager = getUserManager();
 
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 3b7e3b4..0fcc1ec 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -132,6 +132,12 @@
      */
     public abstract boolean isCurrentUserId(@UserIdInt int userId);
 
+    /**
+     * Returns the current user id. Where possible, prefer to use {@link #isCurrentUserId(int)}
+     * instead, as that method has more flexibility.
+     */
+    public abstract @UserIdInt int getCurrentUserId();
+
     protected abstract int[] getProfileIds(@UserIdInt int userId);
 
     /**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index eca6070..c5d7f2c 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1469,18 +1469,9 @@
 
     public @Nullable Location getLastLocation(CallerIdentity identity,
             @PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
-        if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName())) {
+        if (!isActive(ignoreLocationSettings, identity)) {
             return null;
         }
-        if (!ignoreLocationSettings) {
-            if (!isEnabled(identity.getUserId())) {
-                return null;
-            }
-            if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
-                return null;
-            }
-        }
 
         // lastly - note app ops
         if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
@@ -1905,20 +1896,16 @@
             Preconditions.checkState(Thread.holdsLock(mLock));
         }
 
-        CallerIdentity identity = registration.getIdentity();
-
         if (!registration.isPermitted()) {
             return false;
         }
 
-        if (!registration.getRequest().isLocationSettingsIgnored()) {
-            if (!isEnabled(identity.getUserId())) {
-                return false;
-            }
-            if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
-                return false;
-            }
+        boolean locationSettingsIgnored = registration.getRequest().isLocationSettingsIgnored();
+        if (!isActive(locationSettingsIgnored, registration.getIdentity())) {
+            return false;
+        }
 
+        if (!locationSettingsIgnored) {
             switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
                 case LOCATION_MODE_FOREGROUND_ONLY:
                     if (!registration.isForeground()) {
@@ -1944,8 +1931,32 @@
             }
         }
 
-        return !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName());
+        return true;
+    }
+
+    private boolean isActive(boolean locationSettingsIgnored, CallerIdentity identity) {
+        if (identity.isSystemServer()) {
+            if (!locationSettingsIgnored) {
+                if (!isEnabled(mUserHelper.getCurrentUserId())) {
+                    return false;
+                }
+            }
+        } else {
+            if (!locationSettingsIgnored) {
+                if (!isEnabled(identity.getUserId())) {
+                    return false;
+                }
+                if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+                    return false;
+                }
+            }
+            if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+                    identity.getPackageName())) {
+                return false;
+            }
+        }
+
+        return true;
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dfeb682..b1289dd 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7164,6 +7164,16 @@
             return true;
         }
 
+        // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
+        final boolean isBubbleOrOverflowed = record.canBubble() && (record.isFlagBubbleRemoved()
+                || record.getNotification().isBubbleNotification());
+        if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
+                && record.getNotification().getBubbleMetadata() != null) {
+            if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
+                return true;
+            }
+        }
+
         return false;
     }
 
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index de869079..c4a23f9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -774,6 +774,9 @@
                 throw new IllegalArgumentException(
                         "To query by locus ID, package name must also be set");
             }
+            if ((query.getQueryFlags() & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) {
+                ensureStrictAccessShortcutsPermission(callingPackage);
+            }
 
             // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
             return new ParceledListSlice<>((List<ShortcutInfo>)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 06f8ca3..8bc524c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -103,7 +103,6 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
-import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -246,7 +245,6 @@
 import android.graphics.Bitmap;
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -339,7 +337,6 @@
 import com.android.internal.os.Zygote;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
@@ -3842,7 +3839,7 @@
         // are all flushed.  Not really needed, but keeps things nice and
         // tidy.
         t.traceBegin("GC");
-        Runtime.getRuntime().gc();
+        VMRuntime.getRuntime().requestConcurrentGC();
         t.traceEnd();
 
         // The initial scanning above does many calls into installd while
@@ -11592,22 +11589,6 @@
         }
         pkgSetting.signatures.mSigningDetails = reconciledPkg.signingDetails;
 
-        if (!pkg.getAdoptPermissions().isEmpty()) {
-            // This package wants to adopt ownership of permissions from
-            // another package.
-            for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
-                final String origName = pkg.getAdoptPermissions().get(i);
-                final PackageSetting orig = mSettings.getPackageLPr(origName);
-                if (orig != null) {
-                    if (verifyPackageUpdateLPr(orig, pkg)) {
-                        Slog.i(TAG, "Adopting permissions from " + origName + " to "
-                                + pkg.getPackageName());
-                        mPermissionManager.transferPermissions(origName, pkg.getPackageName());
-                    }
-                }
-            }
-        }
-
         if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
             for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
                 final String codePathString = changedAbiCodePath.get(i);
@@ -12854,27 +12835,6 @@
                     reconciledPkg.prepareResult != null && reconciledPkg.prepareResult.replace;
             mAppsFilter.addPackage(pkgSetting, isReplace);
 
-            // Don't allow ephemeral applications to define new permissions groups.
-            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
-                        + " ignored: instant apps cannot define new permission groups.");
-            } else {
-                mPermissionManager.addAllPermissionGroups(pkg, chatty);
-            }
-
-            // If a permission has had its defining app changed, or it has had its protection
-            // upgraded, we need to revoke apps that hold it
-            final List<String> permissionsWithChangedDefinition;
-            // Don't allow ephemeral applications to define new permissions.
-            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                permissionsWithChangedDefinition = null;
-                Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
-                        + " ignored: instant apps cannot define new permissions.");
-            } else {
-                permissionsWithChangedDefinition =
-                        mPermissionManager.addAllPermissions(pkg, chatty);
-            }
-
             int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
             StringBuilder r = null;
             int i;
@@ -12901,31 +12861,7 @@
                 }
             }
 
-            boolean hasOldPkg = oldPkg != null;
-            boolean hasPermissionDefinitionChanges =
-                    !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
-            if (hasOldPkg || hasPermissionDefinitionChanges) {
-                // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
-                // revoke callbacks from this method might need to kill apps which need the
-                // mPackages lock on a different thread. This would dead lock.
-                //
-                // Hence create a copy of all package names and pass it into
-                // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get
-                // revoked. If a new package is added before the async code runs the permission
-                // won't be granted yet, hence new packages are no problem.
-                final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet());
-
-                AsyncTask.execute(() -> {
-                    if (hasOldPkg) {
-                        mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
-                                allPackageNames);
-                    }
-                    if (hasPermissionDefinitionChanges) {
-                        mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged(
-                                permissionsWithChangedDefinition, allPackageNames);
-                    }
-                });
-            }
+            mPermissionManager.onPackageAdded(pkg, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
         }
 
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -13024,7 +12960,7 @@
         }
     }
 
-    void removePackageLI(String packageName, boolean chatty) {
+    private void removePackageLI(String packageName, boolean chatty) {
         if (DEBUG_INSTALL) {
             if (chatty)
                 Log.d(TAG, "Removing package " + packageName);
@@ -13039,9 +12975,9 @@
         }
     }
 
-    void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
+    private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
         mComponentResolver.removeAllComponents(pkg, chatty);
-        mPermissionManager.removeAllPermissions(pkg, chatty);
+        mPermissionManager.onPackageRemoved(pkg);
 
         final int instrumentationSize = ArrayUtils.size(pkg.getInstrumentations());
         StringBuilder r = null;
@@ -17864,6 +17800,17 @@
         }
     }
 
+    /*
+     * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
+     * as this only works for packages that are installed
+     *
+     * TODO: Move logic for permission group compatibility into PermissionManagerService
+     */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private static boolean cannotInstallWithBadPermissionGroups(ParsedPackage parsedPackage) {
+        return parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
+    }
+
     @GuardedBy("mInstallLock")
     private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
             throws PrepareFailure {
@@ -17906,7 +17853,7 @@
                 | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
-        ParsedPackage parsedPackage;
+        final ParsedPackage parsedPackage;
         try (PackageParser2 pp = new PackageParser2(mSeparateProcesses, false, mMetrics, null,
                 mPackageParserCallback)) {
             parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
@@ -18022,15 +17969,6 @@
                 }
             }
 
-            /*
-             * Cannot properly check CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS using CompatChanges
-             * as this only works for packages that are installed
-             *
-             * TODO: Move logic for permission group compatibility into PermissionManagerService
-             */
-            boolean cannotInstallWithBadPermissionGroups =
-                    parsedPackage.getTargetSdkVersion() >= Build.VERSION_CODES.S;
-
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             if (ps != null) {
                 if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
@@ -18088,7 +18026,8 @@
                         parsedPackage.getPermissionGroups().get(groupNum);
                 final PermissionGroupInfo sourceGroup = getPermissionGroupInfo(group.getName(), 0);
 
-                if (sourceGroup != null && cannotInstallWithBadPermissionGroups) {
+                if (sourceGroup != null
+                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
                     final String sourcePackageName = sourceGroup.packageName;
 
                     if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName))
@@ -18163,7 +18102,8 @@
                     }
                 }
 
-                if (perm.getGroup() != null && cannotInstallWithBadPermissionGroups) {
+                if (perm.getGroup() != null
+                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
                     boolean isPermGroupDefinedByPackage = false;
                     for (int groupNum = 0; groupNum < numGroups; groupNum++) {
                         if (parsedPackage.getPermissionGroups().get(groupNum).getName()
@@ -19535,33 +19475,13 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
-                    if ((deletedPs.sharedUser == null || deletedPs.sharedUser.packages.size() == 0)
-                            && !isUpdatedSystemApp(deletedPs)) {
-                        mPermissionManager.removeAppIdStateTEMP(removedAppId);
+                    final SharedUserSetting sus = deletedPs.getSharedUser();
+                    List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : null;
+                    if (sharedUserPkgs == null) {
+                        sharedUserPkgs = Collections.emptyList();
                     }
-                    mPermissionManager.updatePermissions(deletedPs.name, null);
-                    if (deletedPs.sharedUser != null) {
-                        // Remove permissions associated with package. Since runtime
-                        // permissions are per user we have to kill the removed package
-                        // or packages running under the shared user of the removed
-                        // package if revoking the permissions requested only by the removed
-                        // package is successful and this causes a change in gids.
-                        boolean shouldKill = false;
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            final int userIdToKill = mPermissionManager
-                                    .revokeSharedUserPermissionsForDeletedPackageTEMP(deletedPs,
-                                            userId);
-                            shouldKill |= userIdToKill != UserHandle.USER_NULL;
-                        }
-                        // If gids changed, kill all affected packages.
-                        if (shouldKill) {
-                            mHandler.post(() -> {
-                                // This has to happen with no lock held.
-                                killApplication(deletedPs.name, deletedPs.appId,
-                                        KILL_APP_REASON_GIDS_CHANGED);
-                            });
-                        }
-                    }
+                    mPermissionManager.onPackageStateRemoved(packageName, deletedPs.appId,
+                            deletedPs.pkg, sharedUserPkgs);
                     clearPackagePreferredActivitiesLPw(
                             deletedPs.name, changedUsers, UserHandle.USER_ALL);
                 }
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 69721d0..804faa1 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -252,43 +252,6 @@
         return numMatch == NS;
     }
 
-    public boolean sameSet(PreferredComponent pc) {
-        if (mSetPackages == null || pc == null || pc.mSetPackages == null
-                || !sameComponent(pc.mComponent)) {
-            return false;
-        }
-        final int otherPackageCount = pc.mSetPackages.length;
-        final int packageCount = mSetPackages.length;
-        int numMatch = 0;
-        for (int i = 0; i < otherPackageCount; i++) {
-            boolean good = false;
-            for (int j = 0; j < packageCount; j++) {
-                if (mSetPackages[j].equals(pc.mSetPackages[j])
-                        && mSetClasses[j].equals(pc.mSetClasses[j])) {
-                    numMatch++;
-                    good = true;
-                    break;
-                }
-            }
-            if (!good) {
-                return false;
-            }
-        }
-        return numMatch == packageCount;
-    }
-
-    /** Returns true if the preferred component represents the provided ComponentName. */
-    private boolean sameComponent(ComponentName comp) {
-        if (mComponent == null || comp == null) {
-            return false;
-        }
-        if (mComponent.getPackageName().equals(comp.getPackageName())
-                && mComponent.getClassName().equals(comp.getClassName())) {
-            return true;
-        }
-        return false;
-    }
-
     public boolean isSuperset(List<ResolveInfo> query, boolean excludeSetupWizardPackage) {
         if (mSetPackages == null) {
             return query == null;
diff --git a/services/core/java/com/android/server/pm/PreferredIntentResolver.java b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
index ff3df13..a261e29 100644
--- a/services/core/java/com/android/server/pm/PreferredIntentResolver.java
+++ b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
@@ -22,7 +22,6 @@
 import java.io.PrintWriter;
 
 import com.android.server.IntentResolver;
-import java.util.ArrayList;
 
 public class PreferredIntentResolver
         extends IntentResolver<PreferredActivity, PreferredActivity> {
@@ -46,24 +45,4 @@
     protected IntentFilter getIntentFilter(@NonNull PreferredActivity input) {
         return input;
     }
-
-    public boolean shouldAddPreferredActivity(PreferredActivity pa) {
-        ArrayList<PreferredActivity> pal = findFilters(pa);
-        if (pal == null || pal.isEmpty()) {
-            return true;
-        }
-        if (!pa.mPref.mAlways) {
-            return false;
-        }
-        final int activityCount = pal.size();
-        for (int i = 0; i < activityCount; i++) {
-            PreferredActivity cur = pal.get(i);
-            if (cur.mPref.mAlways
-                    && cur.mPref.mMatch == (pa.mPref.mMatch & IntentFilter.MATCH_CATEGORY_MASK)
-                    && cur.mPref.sameSet(pa.mPref)) {
-                return false;
-            }
-        }
-        return true;
-    }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9a5e05f..966090cb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1312,7 +1312,8 @@
                 PreferredActivity pa = new PreferredActivity(parser);
                 if (pa.mPref.getParseError() == null) {
                     final PreferredIntentResolver resolver = editPreferredActivitiesLPw(userId);
-                    if (resolver.shouldAddPreferredActivity(pa)) {
+                    ArrayList<PreferredActivity> pal = resolver.findFilters(pa);
+                    if (pal == null || pal.size() == 0 || pa.mPref.mAlways) {
                         resolver.addFilter(pa);
                     }
                 } else {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ed51039..2d77182 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2837,10 +2837,14 @@
                 int queryFlags, int userId, int callingPid, int callingUid) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
-            final boolean cloneKeyFieldOnly =
-                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
-            final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
-                    : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
+            int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
+            if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) {
+                flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
+            } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) {
+                flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON;
+            }
+            final int cloneFlag = flags;
+
             if (packageName == null) {
                 shortcutIds = null; // LauncherAppsService already threw for it though.
             }
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 0245b28..687e96c 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -399,8 +399,7 @@
     @NonNull
     public static Permission createOrUpdate(@Nullable Permission permission,
             @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg,
-            @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission,
-            boolean chatty) {
+            @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission) {
         // Allow system apps to redefine non-system permissions
         boolean ownerChanged = false;
         if (permission != null && !Objects.equals(permission.mPermissionInfo.packageName,
@@ -437,7 +436,7 @@
                     permission.mPermissionInfo = permissionInfo;
                     permission.mReconciled = true;
                     permission.mUid = pkg.getUid();
-                    if (chatty) {
+                    if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -456,7 +455,7 @@
                         + permissionInfo.packageName + " ignored: original from "
                         + permission.mPermissionInfo.packageName);
             }
-        } else if (chatty) {
+        } else if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
             if (r == null) {
                 r = new StringBuilder(256);
             } else {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6c03a28..1b35d29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -93,6 +93,7 @@
 import android.content.pm.parsing.component.ParsedPermissionGroup;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.metrics.LogMaker;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Debug;
@@ -133,6 +134,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.Preconditions;
@@ -145,7 +147,6 @@
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.PackageSetting;
-import com.android.server.pm.SharedUserSetting;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -2363,14 +2364,9 @@
      *
      * @param newPackage The new package that was installed
      * @param oldPackage The old package that was updated
-     * @param allPackageNames All package names
-     * @param permissionCallback Callback for permission changed
      */
-    private void revokeRuntimePermissionsIfGroupChanged(
-            @NonNull AndroidPackage newPackage,
-            @NonNull AndroidPackage oldPackage,
-            @NonNull ArrayList<String> allPackageNames,
-            @NonNull PermissionCallback permissionCallback) {
+    private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage,
+            @NonNull AndroidPackage oldPackage) {
         final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions());
         final ArrayMap<String, String> oldPermissionNameToGroupName
                 = new ArrayMap<>(numOldPackagePermissions);
@@ -2403,13 +2399,9 @@
                 if (newPermissionGroupName != null
                         && !newPermissionGroupName.equals(oldPermissionGroupName)) {
                     final int[] userIds = mUserManagerInt.getUserIds();
-                    final int numUserIds = userIds.length;
-                    for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
-                        final int userId = userIds[userIdNum];
-
-                        final int numPackages = allPackageNames.size();
-                        for (int packageNum = 0; packageNum < numPackages; packageNum++) {
-                            final String packageName = allPackageNames.get(packageNum);
+                    mPackageManagerInt.forEachPackage(pkg -> {
+                        final String packageName = pkg.getPackageName();
+                        for (final int userId : userIds) {
                             final int permissionState = checkPermission(permissionName, packageName,
                                     userId);
                             if (permissionState == PackageManager.PERMISSION_GRANTED) {
@@ -2422,14 +2414,15 @@
 
                                 try {
                                     revokeRuntimePermissionInternal(permissionName, packageName,
-                                            false, callingUid, userId, null, permissionCallback);
+                                            false, callingUid, userId, null,
+                                            mDefaultPermissionCallback);
                                 } catch (IllegalArgumentException e) {
                                     Slog.e(TAG, "Could not revoke " + permissionName + " from "
                                             + packageName, e);
                                 }
                             }
                         }
-                    }
+                    });
                 }
             }
         }
@@ -2440,18 +2433,11 @@
      * granted permissions must be revoked.
      *
      * @param permissionsToRevoke A list of permission names to revoke
-     * @param allPackageNames All package names
-     * @param permissionCallback Callback for permission changed
      */
-    private void revokeRuntimePermissionsIfPermissionDefinitionChanged(
-            @NonNull List<String> permissionsToRevoke,
-            @NonNull ArrayList<String> allPackageNames,
-            @NonNull PermissionCallback permissionCallback) {
-
+    private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+            @NonNull List<String> permissionsToRevoke) {
         final int[] userIds = mUserManagerInt.getUserIds();
         final int numPermissions = permissionsToRevoke.size();
-        final int numUserIds = userIds.length;
-        final int numPackages = allPackageNames.size();
         final int callingUid = Binder.getCallingUid();
 
         for (int permNum = 0; permNum < numPermissions; permNum++) {
@@ -2462,15 +2448,14 @@
                     continue;
                 }
             }
-            for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
-                final int userId = userIds[userIdNum];
-                for (int packageNum = 0; packageNum < numPackages; packageNum++) {
-                    final String packageName = allPackageNames.get(packageNum);
-                    final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
-                    if (uid < Process.FIRST_APPLICATION_UID) {
-                        // do not revoke from system apps
-                        continue;
-                    }
+            mPackageManagerInt.forEachPackage(pkg -> {
+                final String packageName = pkg.getPackageName();
+                final int appId = pkg.getUid();
+                if (appId < Process.FIRST_APPLICATION_UID) {
+                    // do not revoke from system apps
+                    return;
+                }
+                for (final int userId : userIds) {
                     final int permissionState = checkPermissionImpl(permName, packageName,
                             userId);
                     final int flags = getPermissionFlags(permName, packageName, userId);
@@ -2480,6 +2465,7 @@
                             | FLAG_PERMISSION_GRANTED_BY_ROLE;
                     if (permissionState == PackageManager.PERMISSION_GRANTED
                             && (flags & flagMask) == 0) {
+                        final int uid = UserHandle.getUid(userId, appId);
                         EventLog.writeEvent(0x534e4554, "154505240", uid,
                                 "Revoking permission " + permName + " from package "
                                         + packageName + " due to definition change");
@@ -2490,18 +2476,18 @@
                                 + packageName + " due to definition change");
                         try {
                             revokeRuntimePermissionInternal(permName, packageName,
-                                    false, callingUid, userId, null, permissionCallback);
+                                    false, callingUid, userId, null, mDefaultPermissionCallback);
                         } catch (Exception e) {
                             Slog.e(TAG, "Could not revoke " + permName + " from "
                                     + packageName, e);
                         }
                     }
                 }
-            }
+            });
         }
     }
 
-    private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
+    private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) {
         final int N = ArrayUtils.size(pkg.getPermissions());
         ArrayList<String> definitionChangedPermissions = new ArrayList<>();
         for (int i=0; i<N; i++) {
@@ -2539,7 +2525,7 @@
             synchronized (mLock) {
                 final Permission permission = Permission.createOrUpdate(oldPermission,
                         permissionInfo, pkg, mRegistry.getPermissionTrees(),
-                        isOverridingSystemPermission, chatty);
+                        isOverridingSystemPermission);
                 if (p.isTree()) {
                     mRegistry.addPermissionTree(permission);
                 } else {
@@ -2557,7 +2543,7 @@
         return definitionChangedPermissions;
     }
 
-    private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
+    private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) {
         synchronized (mLock) {
             final int N = ArrayUtils.size(pkg.getPermissionGroups());
             StringBuilder r = null;
@@ -2568,7 +2554,7 @@
                 final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName);
                 if (cur == null || isPackageUpdate) {
                     mRegistry.addPermissionGroup(pg);
-                    if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (DEBUG_PACKAGE_SCANNING) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -2583,7 +2569,7 @@
                     Slog.w(TAG, "Permission group " + pg.getName() + " from package "
                             + pg.getPackageName() + " ignored: original from "
                             + cur.getPackageName());
-                    if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (DEBUG_PACKAGE_SCANNING) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -2600,7 +2586,7 @@
         }
     }
 
-    private void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
+    private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) {
         synchronized (mLock) {
             int N = ArrayUtils.size(pkg.getPermissions());
             StringBuilder r = null;
@@ -2612,7 +2598,7 @@
                 }
                 if (bp != null && bp.isPermission(p)) {
                     bp.setPermissionInfo(null);
-                    if (DEBUG_REMOVE && chatty) {
+                    if (DEBUG_REMOVE) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -3990,34 +3976,30 @@
     }
 
     @UserIdInt
-    private int revokeSharedUserPermissionsForDeletedPackage(@NonNull PackageSetting deletedPs,
+    private int revokeSharedUserPermissionsForDeletedPackageInternal(
+            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
             @UserIdInt int userId) {
-        if ((deletedPs == null) || (deletedPs.pkg == null)) {
+        if (pkg == null) {
             Slog.i(TAG, "Trying to update info for null package. Just ignoring");
             return UserHandle.USER_NULL;
         }
 
-        SharedUserSetting sus = deletedPs.getSharedUser();
-
-        // No sharedUserId
-        if (sus == null) {
+        // No shared user packages
+        if (sharedUserPkgs.isEmpty()) {
             return UserHandle.USER_NULL;
         }
 
         int affectedUserId = UserHandle.USER_NULL;
         // Update permissions
-        for (String eachPerm : deletedPs.pkg.getRequestedPermissions()) {
+        for (String eachPerm : pkg.getRequestedPermissions()) {
             // Check if another package in the shared user needs the permission.
             boolean used = false;
-            final List<AndroidPackage> pkgs = sus.getPackages();
-            if (pkgs != null) {
-                for (AndroidPackage pkg : pkgs) {
-                    if (pkg != null
-                            && !pkg.getPackageName().equals(deletedPs.pkg.getPackageName())
-                            && pkg.getRequestedPermissions().contains(eachPerm)) {
-                        used = true;
-                        break;
-                    }
+            for (AndroidPackage sharedUserpkg : sharedUserPkgs) {
+                if (sharedUserpkg != null
+                        && !sharedUserpkg.getPackageName().equals(pkg.getPackageName())
+                        && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) {
+                    used = true;
+                    break;
                 }
             }
             if (used) {
@@ -4025,7 +4007,7 @@
             }
 
             PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
-                    deletedPs.pkg.getPackageName());
+                    pkg.getPackageName());
 
             // If the package is shadowing is a disabled system package,
             // do not drop permissions that the shadowed package requests.
@@ -4043,9 +4025,9 @@
             }
 
             synchronized (mLock) {
-                UidPermissionState uidState = getUidStateLocked(deletedPs.pkg, userId);
+                UidPermissionState uidState = getUidStateLocked(pkg, userId);
                 if (uidState == null) {
-                    Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
+                    Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
                             + " and user " + userId);
                     continue;
                 }
@@ -4848,10 +4830,113 @@
         }
     }
 
-    private void transferPermissions(@NonNull String oldPackageName,
-            @NonNull String newPackageName) {
-        synchronized (mLock) {
-            mRegistry.transferPermissions(oldPackageName, newPackageName);
+    private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg) {
+        if (!pkg.getAdoptPermissions().isEmpty()) {
+            // This package wants to adopt ownership of permissions from
+            // another package.
+            for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
+                final String origName = pkg.getAdoptPermissions().get(i);
+                if (canAdoptPermissionsInternal(origName, pkg)) {
+                    Slog.i(TAG, "Adopting permissions from " + origName + " to "
+                            + pkg.getPackageName());
+                    synchronized (mLock) {
+                        mRegistry.transferPermissions(origName, pkg.getPackageName());
+                    }
+                }
+            }
+        }
+
+        // Don't allow ephemeral applications to define new permissions groups.
+        if (isInstantApp) {
+            Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
+                    + " ignored: instant apps cannot define new permission groups.");
+        } else {
+            addAllPermissionGroupsInternal(pkg);
+        }
+
+        // If a permission has had its defining app changed, or it has had its protection
+        // upgraded, we need to revoke apps that hold it
+        final List<String> permissionsWithChangedDefinition;
+        // Don't allow ephemeral applications to define new permissions.
+        if (isInstantApp) {
+            permissionsWithChangedDefinition = null;
+            Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
+                    + " ignored: instant apps cannot define new permissions.");
+        } else {
+            permissionsWithChangedDefinition = addAllPermissionsInternal(pkg);
+        }
+
+        boolean hasOldPkg = oldPkg != null;
+        boolean hasPermissionDefinitionChanges =
+                !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
+        if (hasOldPkg || hasPermissionDefinitionChanges) {
+            // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
+            // revoke callbacks from this method might need to kill apps which need the
+            // mPackages lock on a different thread. This would dead lock.
+            AsyncTask.execute(() -> {
+                if (hasOldPkg) {
+                    revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg);
+                }
+                if (hasPermissionDefinitionChanges) {
+                    revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+                            permissionsWithChangedDefinition);
+                }
+            });
+        }
+    }
+
+    private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName,
+            @NonNull AndroidPackage newPkg) {
+        final PackageSetting oldPs = mPackageManagerInt.getPackageSetting(oldPackageName);
+        if (oldPs == null) {
+            return false;
+        }
+        if (!oldPs.isSystem()) {
+            Slog.w(TAG, "Unable to update from " + oldPs.name
+                    + " to " + newPkg.getPackageName()
+                    + ": old package not in system partition");
+            return false;
+        }
+        if (mPackageManagerInt.getPackage(oldPs.name) != null) {
+            Slog.w(TAG, "Unable to update from " + oldPs.name
+                    + " to " + newPkg.getPackageName()
+                    + ": old package still exists");
+            return false;
+        }
+        return true;
+    }
+
+    private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) {
+        removeAllPermissionsInternal(pkg);
+    }
+
+    private void onPackageStateRemovedInternal(@NonNull String packageName, int appId,
+            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs) {
+        if (sharedUserPkgs.isEmpty()
+                && mPackageManagerInt.getDisabledSystemPackage(packageName) == null) {
+            removeAppIdState(appId);
+        }
+        updatePermissions(packageName, null, mDefaultPermissionCallback);
+        if (!sharedUserPkgs.isEmpty()) {
+            // Remove permissions associated with package. Since runtime
+            // permissions are per user we have to kill the removed package
+            // or packages running under the shared user of the removed
+            // package if revoking the permissions requested only by the removed
+            // package is successful and this causes a change in gids.
+            boolean shouldKill = false;
+            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                final int userIdToKill = revokeSharedUserPermissionsForDeletedPackageInternal(pkg,
+                        sharedUserPkgs, userId);
+                shouldKill |= userIdToKill != UserHandle.USER_NULL;
+            }
+            // If gids changed, kill all affected packages.
+            if (shouldKill) {
+                mHandler.post(() -> {
+                    // This has to happen with no lock held.
+                    killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
+                });
+            }
         }
     }
 
@@ -4955,35 +5040,6 @@
         }
 
         @Override
-        public void revokeRuntimePermissionsIfGroupChanged(
-                @NonNull AndroidPackage newPackage,
-                @NonNull AndroidPackage oldPackage,
-                @NonNull ArrayList<String> allPackageNames) {
-            PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage,
-                    oldPackage, allPackageNames, mDefaultPermissionCallback);
-        }
-
-        @Override
-        public void revokeRuntimePermissionsIfPermissionDefinitionChanged(
-                @NonNull List<String> permissionsToRevoke,
-                @NonNull ArrayList<String> allPackageNames) {
-            PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged(
-                    permissionsToRevoke, allPackageNames, mDefaultPermissionCallback);
-        }
-
-        @Override
-        public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
-            return PermissionManagerService.this.addAllPermissions(pkg, chatty);
-        }
-        @Override
-        public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
-            PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
-        }
-        @Override
-        public void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
-            PermissionManagerService.this.removeAllPermissions(pkg, chatty);
-        }
-        @Override
         public void readLegacyPermissionStateTEMP() {
             PermissionManagerService.this.readLegacyPermissionState();
         }
@@ -4995,17 +5051,6 @@
         public void onUserRemoved(@UserIdInt int userId) {
             PermissionManagerService.this.onUserRemoved(userId);
         }
-        @Override
-        public void removeAppIdStateTEMP(@AppIdInt int appId) {
-            PermissionManagerService.this.removeAppIdState(appId);
-        }
-        @Override
-        @UserIdInt
-        public int revokeSharedUserPermissionsForDeletedPackageTEMP(
-                @NonNull PackageSetting deletedPs, @UserIdInt int userId) {
-            return PermissionManagerService.this.revokeSharedUserPermissionsForDeletedPackage(
-                    deletedPs, userId);
-        }
         @NonNull
         @Override
         public Set<String> getGrantedPermissions(@NonNull String packageName,
@@ -5313,9 +5358,24 @@
         }
 
         @Override
-        public void transferPermissions(@NonNull String oldPackageName,
-                @NonNull String newPackageName) {
-            PermissionManagerService.this.transferPermissions(oldPackageName, newPackageName);
+        public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+                @Nullable AndroidPackage oldPkg) {
+            Objects.requireNonNull(pkg);
+            onPackageAddedInternal(pkg, isInstantApp, oldPkg);
+        }
+
+        @Override
+        public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+            Objects.requireNonNull(pkg);
+            onPackageRemovedInternal(pkg);
+        }
+
+        @Override
+        public void onPackageStateRemoved(@NonNull String packageName, int appId,
+                @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs) {
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(sharedUserPkgs);
+            onPackageStateRemovedInternal(packageName, appId, pkg, sharedUserPkgs);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index df9d0d3..1becbed 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -24,7 +24,6 @@
 import android.content.pm.PermissionInfo;
 import android.permission.PermissionManagerInternal;
 
-import com.android.server.pm.PackageSetting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.ArrayList;
@@ -277,44 +276,6 @@
     public abstract void resetAllRuntimePermissions(@UserIdInt int userId);
 
     /**
-     * We might auto-grant permissions if any permission of the group is already granted. Hence if
-     * the group of a granted permission changes we need to revoke it to avoid having permissions of
-     * the new group auto-granted.
-     *
-     * @param newPackage The new package that was installed
-     * @param oldPackage The old package that was updated
-     * @param allPackageNames All packages
-     */
-    public abstract void revokeRuntimePermissionsIfGroupChanged(
-            @NonNull AndroidPackage newPackage,
-            @NonNull AndroidPackage oldPackage,
-            @NonNull ArrayList<String> allPackageNames);
-
-    /**
-     * Some permissions might have been owned by a non-system package, and the system then defined
-     * said permission. Some other permissions may one have been install permissions, but are now
-     * runtime or higher. These permissions should be revoked.
-     *
-     * @param permissionsToRevoke A list of permission names to revoke
-     * @param allPackageNames All packages
-     */
-    public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged(
-            @NonNull List<String> permissionsToRevoke,
-            @NonNull ArrayList<String> allPackageNames);
-
-    /**
-     * Add all permissions in the given package.
-     * <p>
-     * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
-     * the permission settings.
-     *
-     * @return A list of BasePermissions that were updated, and need to be revoked from packages
-     */
-    public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
-    public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
-    public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
-
-    /**
      * Read legacy permission state from package settings.
      *
      * TODO(zhanghai): This is a temporary method because we should not expose
@@ -337,30 +298,6 @@
     public abstract void onUserRemoved(@UserIdInt int userId);
 
     /**
-     * Remove the permission state associated with an app ID, called the same time as the
-     * removal of a {@code PackageSetitng}.
-     *
-     * TODO(zhanghai): This is a temporary method before we figure out a way to get notified of app
-     * ID removal via API.
-     */
-    public abstract void removeAppIdStateTEMP(@AppIdInt int appId);
-
-    /**
-     * Update the shared user setting when a package with a shared user id is removed. The gids
-     * associated with each permission of the deleted package are removed from the shared user'
-     * gid list only if its not in use by other permissions of packages in the shared user setting.
-     *
-     * TODO(zhanghai): We should not need this when permission no longer sees an incomplete package
-     * state where the updated system package is uninstalled but the disabled system package is yet
-     * to be installed. Then we should handle this in restorePermissionState().
-     *
-     * @return the affected user id, may be a real user ID, USER_ALL, or USER_NULL when none.
-     */
-    @UserIdInt
-    public abstract int revokeSharedUserPermissionsForDeletedPackageTEMP(
-            @NonNull PackageSetting deletedPs, @UserIdInt int userId);
-
-    /**
      * Get all the permissions granted to a package.
      *
      * @param packageName the name of the package
@@ -578,10 +515,35 @@
             @NonNull LegacyPermissionSettings legacyPermissionSettings);
 
     /**
-     * Transfers ownership of permissions from one package to another.
+     * Callback when a package has been added.
+     *
+     * @param pkg the added package
+     * @param isInstantApp whether the added package is an instant app
+     * @param oldPkg the old package, or {@code null} if none
      */
-    public abstract void transferPermissions(@NonNull String oldPackageName,
-            @NonNull String newPackageName);
+    //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public abstract void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg);
+
+    /**
+     * Callback when a package has been removed.
+     *
+     * @param pkg the removed package
+     */
+    //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public abstract void onPackageRemoved(@NonNull AndroidPackage pkg);
+
+    /**
+     * Callback when the state for a package has been removed.
+     *
+     * @param packageName the name of the removed package
+     * @param appId the app ID of the removed package
+     * @param pkg the removed package, or {@code null} if unavailable
+     * @param sharedUserPkgs the packages that are in the same shared user
+     */
+    //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public abstract void onPackageStateRemoved(@NonNull String packageName, int appId,
+            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs);
 
     /**
      * Check whether a permission can be propagated to instant app.
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 54f6183..154f9a4 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.policy;
 
 import android.annotation.NonNull;
+import android.content.Context;
 
 import com.android.server.devicestate.DeviceStatePolicy;
 import com.android.server.devicestate.DeviceStateProvider;
@@ -27,10 +28,12 @@
  * @see DeviceStateProviderImpl
  */
 public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
+    private final Context mContext;
     private final DeviceStateProvider mProvider;
 
-    public DeviceStatePolicyImpl() {
-        mProvider = new DeviceStateProviderImpl();
+    public DeviceStatePolicyImpl(Context context) {
+        mContext = context;
+        mProvider = DeviceStateProviderImpl.create(mContext);
     }
 
     public DeviceStateProvider getDeviceStateProvider() {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 85ab0bc..321bb8c 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -16,30 +16,464 @@
 
 package com.android.server.policy;
 
-import android.annotation.Nullable;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManagerInternal;
+import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.policy.devicestate.config.Conditions;
+import com.android.server.policy.devicestate.config.DeviceState;
+import com.android.server.policy.devicestate.config.DeviceStateConfig;
+import com.android.server.policy.devicestate.config.LidSwitchCondition;
+import com.android.server.policy.devicestate.config.NumericRange;
+import com.android.server.policy.devicestate.config.SensorCondition;
+import com.android.server.policy.devicestate.config.XmlParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BooleanSupplier;
+
+import javax.xml.datatype.DatatypeConfigurationException;
 
 /**
- * Default implementation of {@link DeviceStateProvider}. Currently only supports
- * {@link #DEFAULT_DEVICE_STATE}.
- *
- * @see DeviceStatePolicyImpl
+ * Implementation of {@link DeviceStateProvider} that reads the set of supported device states
+ * from a configuration file provided at either /vendor/etc/devicestate or
+ * /data/system/devicestate/. By default, the provider supports {@link #DEFAULT_DEVICE_STATE} when
+ * no configuration is provided.
  */
-final class DeviceStateProviderImpl implements DeviceStateProvider {
-    private static final int DEFAULT_DEVICE_STATE = 0;
+public final class DeviceStateProviderImpl implements DeviceStateProvider,
+        InputManagerInternal.LidSwitchCallback, SensorEventListener {
+    private static final String TAG = "DeviceStateProviderImpl";
+
+    private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
+
+    @VisibleForTesting
+    static final int DEFAULT_DEVICE_STATE = 0;
+
+    private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
+    private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
+    private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
+
+    /** Interface that allows reading the device state configuration. */
+    interface ReadableConfig {
+        @NonNull
+        InputStream openRead() throws IOException;
+    }
+
+    /**
+     * Returns a new {@link DeviceStateProviderImpl} instance.
+     *
+     * @param context the {@link Context} that should be used to access system services.
+     */
+    public static DeviceStateProviderImpl create(@NonNull Context context) {
+        File configFile = getConfigurationFile();
+        if (configFile == null) {
+            return createFromConfig(context, null);
+        }
+        return createFromConfig(context, new ReadableFileConfig(configFile));
+    }
+
+    /**
+     * Returns a new {@link DeviceStateProviderImpl} instance.
+     *
+     * @param context the {@link Context} that should be used to access system services.
+     * @param readableConfig the config the provider instance should read supported states from.
+     */
+    @VisibleForTesting
+    static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
+            @Nullable ReadableConfig readableConfig) {
+        SparseArray<Conditions> conditionsForState = new SparseArray<>();
+        if (readableConfig != null) {
+            DeviceStateConfig config = parseConfig(readableConfig);
+            if (config != null) {
+                for (DeviceState stateConfig : config.getDeviceState()) {
+                    int state = stateConfig.getIdentifier().intValue();
+                    Conditions conditions = stateConfig.getConditions();
+                    conditionsForState.put(state, conditions);
+                }
+            }
+        }
+
+        if (conditionsForState.size() == 0) {
+            conditionsForState.put(DEFAULT_DEVICE_STATE, null);
+        }
+        return new DeviceStateProviderImpl(context, conditionsForState);
+    }
+
+    // Lock for internal state.
+    private final Object mLock = new Object();
+    private final Context mContext;
+    // List of supported states in ascending order.
+    private final int[] mOrderedStates;
+    // Map of state to a boolean supplier that returns true when all required conditions are met for
+    // the device to be in the state.
+    private final SparseArray<BooleanSupplier> mStateConditions;
 
     @Nullable
+    @GuardedBy("mLock")
     private Listener mListener = null;
+    @GuardedBy("mLock")
+    private int mLastReportedState = INVALID_DEVICE_STATE;
+
+    @GuardedBy("mLock")
+    private boolean mIsLidOpen;
+    @GuardedBy("mLock")
+    private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
+
+    private DeviceStateProviderImpl(@NonNull Context context,
+            @NonNull SparseArray<Conditions> conditionsForState) {
+        mContext = context;
+        mOrderedStates = new int[conditionsForState.size()];
+        for (int i = 0; i < conditionsForState.size(); i++) {
+            mOrderedStates[i] = conditionsForState.keyAt(i);
+        }
+
+        // Whether or not this instance should register to receive lid switch notifications from
+        // InputManagerInternal. If there are no device state conditions that are based on the lid
+        // switch there is no need to register for a callback.
+        boolean shouldListenToLidSwitch = false;
+
+        final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        // The set of Sensor(s) that this instance should register to receive SensorEvent(s) from.
+        final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
+
+        mStateConditions = new SparseArray<>();
+        for (int i = 0; i < mOrderedStates.length; i++) {
+            int state = mOrderedStates[i];
+            Conditions conditions = conditionsForState.get(state);
+            if (conditions == null) {
+                mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+                continue;
+            }
+
+            List<BooleanSupplier> suppliers = new ArrayList<>();
+
+            LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch();
+            if (lidSwitchCondition != null) {
+                suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
+                shouldListenToLidSwitch = true;
+            }
+
+            List<SensorCondition> sensorConditions = conditions.getSensor();
+            for (int j = 0; j < sensorConditions.size(); j++) {
+                SensorCondition sensorCondition = sensorConditions.get(j);
+                final int expectedSensorType = sensorCondition.getType().intValue();
+                final String expectedSensorName = sensorCondition.getName();
+
+                List<Sensor> sensors = sensorManager.getSensorList(expectedSensorType);
+                Sensor foundSensor = null;
+                for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) {
+                    Sensor sensor = sensors.get(sensorIndex);
+                    if (sensor.getName().equals(expectedSensorName)) {
+                        foundSensor = sensor;
+                        break;
+                    }
+                }
+
+                if (foundSensor == null) {
+                    throw new IllegalStateException("Failed to find Sensor with type: "
+                            + expectedSensorType + " and name: " + expectedSensorName);
+                }
+
+                suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
+                sensorsToListenTo.add(foundSensor);
+            }
+
+            if (suppliers.size() > 1) {
+                mStateConditions.put(state, new AndBooleanSupplier(suppliers));
+            } else if (suppliers.size() > 0) {
+                // No need to wrap with an AND supplier if there is only 1.
+                mStateConditions.put(state, suppliers.get(0));
+            } else {
+                // There are no conditions for this state. Default to always true.
+                mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+            }
+        }
+
+        if (shouldListenToLidSwitch) {
+            InputManagerInternal inputManager = LocalServices.getService(
+                    InputManagerInternal.class);
+            inputManager.registerLidSwitchCallback(this);
+        }
+
+        for (int i = 0; i < sensorsToListenTo.size(); i++) {
+            Sensor sensor = sensorsToListenTo.valueAt(i);
+            sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
+        }
+    }
 
     @Override
     public void setListener(Listener listener) {
-        if (mListener != null) {
-            throw new RuntimeException("Provider already has a listener set.");
+        synchronized (mLock) {
+            if (mListener != null) {
+                throw new RuntimeException("Provider already has a listener set.");
+            }
+            mListener = listener;
+        }
+        notifySupportedStatesChanged();
+        notifyDeviceStateChangedIfNeeded();
+    }
+
+    /** Notifies the listener that the set of supported device states has changed. */
+    private void notifySupportedStatesChanged() {
+        int[] supportedStates;
+        synchronized (mLock) {
+            if (mListener == null) {
+                return;
+            }
+
+            supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
         }
 
-        mListener = listener;
-        mListener.onSupportedDeviceStatesChanged(new int[]{ DEFAULT_DEVICE_STATE });
-        mListener.onStateChanged(DEFAULT_DEVICE_STATE);
+        mListener.onSupportedDeviceStatesChanged(supportedStates);
+    }
+
+    /** Computes the current device state and notifies the listener of a change, if needed. */
+    void notifyDeviceStateChangedIfNeeded() {
+        int stateToReport = INVALID_DEVICE_STATE;
+        synchronized (mLock) {
+            if (mListener == null) {
+                return;
+            }
+
+            int newState = mOrderedStates[0];
+            for (int i = 1; i < mOrderedStates.length; i++) {
+                int state = mOrderedStates[i];
+                if (mStateConditions.get(state).getAsBoolean()) {
+                    newState = state;
+                    break;
+                }
+            }
+
+            if (newState != mLastReportedState) {
+                mLastReportedState = newState;
+                stateToReport = newState;
+            }
+        }
+
+        if (stateToReport != INVALID_DEVICE_STATE) {
+            mListener.onStateChanged(stateToReport);
+        }
+    }
+
+    @Override
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+        synchronized (mLock) {
+            mIsLidOpen = lidOpen;
+        }
+        notifyDeviceStateChangedIfNeeded();
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        synchronized (mLock) {
+            mLatestSensorEvent.put(event.sensor, event);
+        }
+        notifyDeviceStateChangedIfNeeded();
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // Do nothing.
+    }
+
+    /**
+     * Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
+     * switch open state matches {@link #mIsLidOpen}.
+     */
+    private final class LidSwitchBooleanSupplier implements BooleanSupplier {
+        private final boolean mExpectedOpen;
+
+        LidSwitchBooleanSupplier(boolean expectedOpen) {
+            mExpectedOpen = expectedOpen;
+        }
+
+        @Override
+        public boolean getAsBoolean() {
+            synchronized (mLock) {
+                return mIsLidOpen == mExpectedOpen;
+            }
+        }
+    }
+
+    /**
+     * Implementation of {@link BooleanSupplier} that returns {@code true} if the latest
+     * {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to
+     * the supplied {@link NumericRange ranges}.
+     */
+    private final class SensorBooleanSupplier implements BooleanSupplier {
+        @NonNull
+        private final Sensor mSensor;
+        @NonNull
+        private final List<NumericRange> mExpectedValues;
+
+        SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) {
+            mSensor = sensor;
+            mExpectedValues = expectedValues;
+        }
+
+        @Override
+        public boolean getAsBoolean() {
+            synchronized (mLock) {
+                SensorEvent latestEvent = mLatestSensorEvent.get(mSensor);
+                if (latestEvent == null) {
+                    // Default to returning false if we have not yet received a sensor event for the
+                    // sensor.
+                    return false;
+                }
+
+                if (latestEvent.values.length != mExpectedValues.size()) {
+                    throw new IllegalStateException("Number of supplied numeric range(s) does not "
+                            + "match the number of values in the latest sensor event for sensor: "
+                            + mSensor);
+                }
+
+                for (int i = 0; i < latestEvent.values.length; i++) {
+                    if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+
+        /**
+         * Returns {@code true} if the supplied {@code value} adheres to the constraints specified
+         * in {@code range}.
+         */
+        private boolean adheresToRange(float value, @NonNull NumericRange range) {
+            final BigDecimal min = range.getMin_optional();
+            if (min != null) {
+                if (value <= min.floatValue()) {
+                    return false;
+                }
+            }
+
+            final BigDecimal minInclusive = range.getMinInclusive_optional();
+            if (minInclusive != null) {
+                if (value < minInclusive.floatValue()) {
+                    return false;
+                }
+            }
+
+            final BigDecimal max = range.getMax_optional();
+            if (max != null) {
+                if (value >= max.floatValue()) {
+                    return false;
+                }
+            }
+
+            final BigDecimal maxInclusive = range.getMaxInclusive_optional();
+            if (maxInclusive != null) {
+                if (value > maxInclusive.floatValue()) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Implementation of {@link BooleanSupplier} whose result is the product of an AND operation
+     * applied to the result of all child suppliers.
+     */
+    private static final class AndBooleanSupplier implements BooleanSupplier {
+        @NonNull
+        List<BooleanSupplier> mBooleanSuppliers;
+
+        AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) {
+            mBooleanSuppliers = booleanSuppliers;
+        }
+
+        @Override
+        public boolean getAsBoolean() {
+            for (int i = 0; i < mBooleanSuppliers.size(); i++) {
+                if (!mBooleanSuppliers.get(i).getAsBoolean()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Returns the device state configuration file that should be used, or {@code null} if no file
+     * is present on the device.
+     * <p>
+     * Defaults to returning a config file present in the data/ dir at
+     * {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir
+     * at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir.
+     */
+    @Nullable
+    private static File getConfigurationFile() {
+        final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
+                DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
+        if (configFileFromDataDir.exists()) {
+            return configFileFromDataDir;
+        }
+
+        final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(),
+                VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
+        if (configFileFromVendorDir.exists()) {
+            return configFileFromVendorDir;
+        }
+
+        return null;
+    }
+
+    /**
+     * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
+     * {@code null} if the file could not be successfully parsed.
+     */
+    @Nullable
+    private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) {
+        try (InputStream in = readableConfig.openRead();
+                InputStream bin = new BufferedInputStream(in)) {
+            return XmlParser.read(bin);
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading device state config", e);
+        }
+        return null;
+    }
+
+    /** Implementation of {@link ReadableConfig} that reads config data from a file. */
+    private static final class ReadableFileConfig implements ReadableConfig {
+        @NonNull
+        private final File mFile;
+
+        private ReadableFileConfig(@NonNull File file) {
+            mFile = file;
+        }
+
+        @Override
+        public InputStream openRead() throws IOException {
+            return new FileInputStream(mFile);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
new file mode 100644
index 0000000..6477552
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.DisplayGroup;
+
+/**
+ * Responsible for creating {@link DisplayPowerRequest}s and associating them with
+ * {@link com.android.server.display.DisplayGroup}s.
+ *
+ * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest}
+ * which is used to request power state changes to every display in the group.
+ */
+class DisplayPowerRequestMapper {
+
+    private final Object mLock = new Object();
+
+    /** A mapping from LogicalDisplay Id to DisplayGroup Id. */
+    @GuardedBy("mLock")
+    private final SparseIntArray mDisplayGroupIds = new SparseIntArray();
+
+    /** A mapping from DisplayGroup Id to DisplayPowerRequest. */
+    @GuardedBy("mLock")
+    private final SparseArray<DisplayPowerRequest> mDisplayPowerRequests = new SparseArray<>();
+
+    private final DisplayManagerInternal mDisplayManagerInternal;
+
+    private final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+
+                @Override
+                public void onDisplayAdded(int displayId) {
+                    synchronized (mLock) {
+                        if (mDisplayGroupIds.indexOfKey(displayId) >= 0) {
+                            return;
+                        }
+                        final int displayGroupId = mDisplayManagerInternal.getDisplayGroupId(
+                                displayId);
+                        if (!mDisplayPowerRequests.contains(displayGroupId)) {
+                            // A new DisplayGroup was created; create a new DisplayPowerRequest.
+                            mDisplayPowerRequests.append(displayGroupId, new DisplayPowerRequest());
+                        }
+                        mDisplayGroupIds.append(displayId, displayGroupId);
+                    }
+                }
+
+                @Override
+                public void onDisplayRemoved(int displayId) {
+                    synchronized (mLock) {
+                        final int index = mDisplayGroupIds.indexOfKey(displayId);
+                        if (index < 0) {
+                            return;
+                        }
+                        final int displayGroupId = mDisplayGroupIds.valueAt(index);
+                        mDisplayGroupIds.removeAt(index);
+
+                        if (mDisplayGroupIds.indexOfValue(displayGroupId) < 0) {
+                            // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
+                            mDisplayPowerRequests.delete(displayGroupId);
+                        }
+                    }
+                }
+
+                @Override
+                public void onDisplayChanged(int displayId) {
+                    synchronized (mLock) {
+                        final int newDisplayGroupId = mDisplayManagerInternal.getDisplayGroupId(
+                                displayId);
+                        final int oldDisplayGroupId = mDisplayGroupIds.get(displayId);
+
+                        if (!mDisplayPowerRequests.contains(newDisplayGroupId)) {
+                            // A new DisplayGroup was created; create a new DisplayPowerRequest.
+                            mDisplayPowerRequests.append(newDisplayGroupId,
+                                    new DisplayPowerRequest());
+                        }
+                        mDisplayGroupIds.put(displayId, newDisplayGroupId);
+
+                        if (mDisplayGroupIds.indexOfValue(oldDisplayGroupId) < 0) {
+                            // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
+                            mDisplayPowerRequests.delete(oldDisplayGroupId);
+                        }
+                    }
+                }
+            };
+
+    DisplayPowerRequestMapper(DisplayManager displayManager,
+            DisplayManagerInternal displayManagerInternal, Handler handler) {
+        mDisplayManagerInternal = displayManagerInternal;
+        displayManager.registerDisplayListener(mDisplayListener, handler);
+        mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest());
+        mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT);
+    }
+
+    DisplayPowerRequest get(int displayId) {
+        synchronized (mLock) {
+            return mDisplayPowerRequests.get(mDisplayGroupIds.get(displayId));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ccd659d..955e1cd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -42,6 +42,7 @@
 import android.hardware.SensorManager;
 import android.hardware.SystemSensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.hardware.power.Boost;
@@ -348,9 +349,9 @@
     // A bitfield that summarizes the effect of the user activity timer.
     private int mUserActivitySummary;
 
-    // The desired display power state.  The actual state may lag behind the
+    // Manages the desired power state of displays. The actual state may lag behind the
     // requested because it is updated asynchronously by the display power controller.
-    private final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
+    private DisplayPowerRequestMapper mDisplayPowerRequestMapper;
 
     // True if the display power state has been fully applied, which means the display
     // is actually on or actually off or whatever was requested.
@@ -1054,6 +1055,8 @@
             mPolicy = getLocalService(WindowManagerPolicy.class);
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
+            mDisplayPowerRequestMapper = new DisplayPowerRequestMapper(mContext.getSystemService(
+                    DisplayManager.class), mDisplayManagerInternal, mHandler);
 
             SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
 
@@ -2296,10 +2299,12 @@
                         && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
                     nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
                     if (now < nextTimeout) {
-                        if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
-                                || mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
+                        final DisplayPowerRequest displayPowerRequest =
+                                mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+                        if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
+                                || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
                             mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
-                        } else if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
+                        } else if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
                             mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
                         }
                     }
@@ -2771,11 +2776,13 @@
      * Returns true if the device is allowed to dream in its current state.
      */
     private boolean canDreamLocked() {
+        final DisplayPowerRequest displayPowerRequest =
+                mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
         if (getWakefulnessLocked() != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
-                || !mDisplayPowerRequest.isBrightOrDim()
-                || mDisplayPowerRequest.isVr()
+                || !displayPowerRequest.isBrightOrDim()
+                || displayPowerRequest.isVr()
                 || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
                         | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0
                 || !mBootCompleted) {
@@ -2826,7 +2833,9 @@
                 sQuiescent = false;
             }
 
-            mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked();
+            final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+                    Display.DEFAULT_DISPLAY);
+            displayPowerRequest.policy = getDesiredScreenPolicyLocked();
 
             // Determine appropriate screen brightness and auto-brightness adjustments.
             final boolean autoBrightness;
@@ -2846,39 +2855,39 @@
             }
 
             // Update display power request.
-            mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
-            mDisplayPowerRequest.useAutoBrightness = autoBrightness;
-            mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
-            mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
+            displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
+            displayPowerRequest.useAutoBrightness = autoBrightness;
+            displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
+            displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
 
-            updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);
+            updatePowerRequestFromBatterySaverPolicy(displayPowerRequest);
 
-            if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
-                mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+            if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
+                displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
                 if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0
                         && !mDrawWakeLockOverrideFromSidekick) {
-                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
-                        mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                    if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+                        displayPowerRequest.dozeScreenState = Display.STATE_DOZE;
                     }
-                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
-                        mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+                    if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+                        displayPowerRequest.dozeScreenState = Display.STATE_ON;
                     }
                 }
-                mDisplayPowerRequest.dozeScreenBrightness =
+                displayPowerRequest.dozeScreenBrightness =
                         mDozeScreenBrightnessOverrideFromDreamManagerFloat;
             } else {
-                mDisplayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
-                mDisplayPowerRequest.dozeScreenBrightness =
+                displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
+                displayPowerRequest.dozeScreenBrightness =
                         PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
 
-            mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
+            mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
                     mRequestWaitForNegativeProximity);
             mRequestWaitForNegativeProximity = false;
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady
-                        + ", policy=" + mDisplayPowerRequest.policy
+                        + ", policy=" + displayPowerRequest.policy
                         + ", mWakefulness=" + getWakefulnessLocked()
                         + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
                         + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
@@ -3049,7 +3058,9 @@
         final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
         final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
         final boolean autoSuspend = !needDisplaySuspendBlocker;
-        final boolean interactive = mDisplayPowerRequest.isBrightOrDim();
+        final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+                Display.DEFAULT_DISPLAY);
+        final boolean interactive = displayPowerRequest.isBrightOrDim();
 
         // Disable auto-suspend if needed.
         // FIXME We should consider just leaving auto-suspend enabled forever since
@@ -3108,19 +3119,21 @@
         if (!mDisplayReady) {
             return true;
         }
-        if (mDisplayPowerRequest.isBrightOrDim()) {
+        final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
+                Display.DEFAULT_DISPLAY);
+        if (displayPowerRequest.isBrightOrDim()) {
             // If we asked for the screen to be on but it is off due to the proximity
             // sensor then we may suspend but only if the configuration allows it.
             // On some hardware it may not be safe to suspend because the proximity
             // sensor may not be correctly configured as a wake-up source.
-            if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
+            if (!displayPowerRequest.useProximitySensor || !mProximityPositive
                     || !mSuspendWhenScreenOffDueToProximityConfig) {
                 return true;
             }
         }
 
-        if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
-                && mDisplayPowerRequest.dozeScreenState == Display.STATE_ON) {
+        if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
+                && displayPowerRequest.dozeScreenState == Display.STATE_ON) {
             // Although we are in DOZE and would normally allow the device to suspend,
             // the doze service has explicitly requested the display to remain in the ON
             // state which means we should hold the display suspend blocker.
@@ -5575,7 +5588,10 @@
             // DisplayPowerController only reports proximity positive (near) if it's
             // positive and the proximity wasn't already being ignored. So it reliably
             // also tells us that we're not already ignoring the proximity sensor.
-            if (mDisplayPowerRequest.useProximitySensor && mProximityPositive) {
+
+            final DisplayPowerRequest displayPowerRequest =
+                    mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+            if (displayPowerRequest.useProximitySensor && mProximityPositive) {
                 mDisplayManagerInternal.ignoreProximitySensorUntilChanged();
                 return true;
             }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index afbe552..510893b 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -87,6 +87,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
 import com.android.server.SystemService;
@@ -337,6 +338,7 @@
                 inputState = new TvInputState();
             }
             inputState.info = info;
+            inputState.uid = getInputUid(info);
             inputMap.put(info.getId(), inputState);
         }
 
@@ -371,6 +373,16 @@
         userState.inputMap = inputMap;
     }
 
+    private int getInputUid(TvInputInfo info) {
+        try {
+            return getContext().getPackageManager().getApplicationInfo(
+                    info.getServiceInfo().packageName, 0).uid;
+        } catch (NameNotFoundException e) {
+            Slog.w(TAG, "Unable to get UID for  " + info, e);
+            return Process.INVALID_UID;
+        }
+    }
+
     @GuardedBy("mLock")
     private void buildTvContentRatingSystemListLocked(int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
@@ -405,7 +417,7 @@
                 return;
             }
             if (mUserStates.contains(mCurrentUserId)) {
-                UserState userState = mUserStates.get(mCurrentUserId);
+                UserState userState = getUserStateLocked(mCurrentUserId);
                 List<SessionState> sessionStatesToRelease = new ArrayList<>();
                 for (SessionState sessionState : userState.sessionStateMap.values()) {
                     if (sessionState.session != null && !sessionState.isRecordingSession) {
@@ -474,7 +486,7 @@
 
     private void removeUser(int userId) {
         synchronized (mLock) {
-            UserState userState = mUserStates.get(userId);
+            UserState userState = getUserStateLocked(userId);
             if (userState == null) {
                 return;
             }
@@ -535,7 +547,7 @@
 
     @GuardedBy("mLock")
     private UserState getOrCreateUserStateLocked(int userId) {
-        UserState userState = mUserStates.get(userId);
+        UserState userState = getUserStateLocked(userId);
         if (userState == null) {
             userState = new UserState(mContext, userId);
             mUserStates.put(userId, userState);
@@ -715,7 +727,8 @@
     }
 
     @GuardedBy("mLock")
-    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+    @Nullable
+    private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
         SessionState sessionState = null;
         try {
             sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
@@ -738,6 +751,7 @@
             }
         }
         removeSessionStateLocked(sessionToken, userId);
+        return sessionState;
     }
 
     @GuardedBy("mLock")
@@ -908,6 +922,7 @@
             return;
         }
         inputState.info = inputInfo;
+        inputState.uid = getInputUid(inputInfo);
 
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
@@ -1248,7 +1263,22 @@
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
                     userId, "createSession");
             final long identity = Binder.clearCallingIdentity();
-            // Generate a unique session id with a random UUID.
+            /**
+             * A randomly generated id for this this session.
+             *
+             * <p>This field contains no user or device reference and is large enough to be
+             * effectively globally unique.
+             *
+             * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+             * Inspect the code at:
+             *
+             * <ul>
+             *   <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+             *   <li>{@link #logTuneStateChanged}
+             *   <li>{@link TvInputManagerService.BinderService#createSession}
+             *   <li>{@link SessionState#sessionId}
+             * </ul>
+             */
             String uniqueSessionId = UUID.randomUUID().toString();
             try {
                 synchronized (mLock) {
@@ -1269,6 +1299,8 @@
                     TvInputInfo info = inputState.info;
                     ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
                     if (serviceState == null) {
+                        int tisUid = PackageManager.getApplicationInfoAsUserCached(
+                                info.getComponent().getPackageName(), 0, resolvedUserId).uid;
                         serviceState = new ServiceState(info.getComponent(), resolvedUserId);
                         userState.serviceStateMap.put(info.getComponent(), serviceState);
                     }
@@ -1301,6 +1333,8 @@
                     } else {
                         updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
                     }
+                    logTuneStateChanged(FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__CREATED,
+                            sessionState, inputState);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1317,8 +1351,17 @@
                     userId, "releaseSession");
             final long identity = Binder.clearCallingIdentity();
             try {
+                SessionState sessionState = null;
+                UserState userState = null;
                 synchronized (mLock) {
-                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                    sessionState = releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                    userState = getUserStateLocked(userId);
+                }
+                if (sessionState != null) {
+                    TvInputState tvInputState = TvInputManagerService.getTvInputState(sessionState,
+                            userState);
+                    logTuneStateChanged(FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__RELEASED,
+                            sessionState, tvInputState);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1372,10 +1415,13 @@
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "setSurface");
             final long identity = Binder.clearCallingIdentity();
+            SessionState sessionState = null;
+            UserState userState = null;
             try {
                 synchronized (mLock) {
                     try {
-                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                        userState = getUserStateLocked(userId);
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 resolvedUserId);
                         if (sessionState.hardwareSessionToken == null) {
                             getSessionLocked(sessionState).setSurface(surface);
@@ -1392,6 +1438,14 @@
                     // surface is not used in TvInputManagerService.
                     surface.release();
                 }
+                if (sessionState != null) {
+                    int state = surface == null
+                            ?
+                            FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__SURFACE_ATTACHED
+                            : FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__SURFACE_DETACHED;
+                    logTuneStateChanged(state, sessionState,
+                            TvInputManagerService.getTvInputState(sessionState, userState));
+                }
                 Binder.restoreCallingIdentity(identity);
             }
         }
@@ -1479,6 +1533,10 @@
                             return;
                         }
 
+                        logTuneStateChanged(
+                                FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__TUNE_STARTED,
+                                sessionState,
+                                TvInputManagerService.getTvInputState(sessionState, userState));
                         // Log the start of watch.
                         SomeArgs args = SomeArgs.obtain();
                         args.arg1 = sessionState.componentName.getPackageName();
@@ -2302,6 +2360,16 @@
         }
     }
 
+    @Nullable
+    private static TvInputState getTvInputState(
+            SessionState sessionState,
+            @Nullable UserState userState) {
+        if (userState != null) {
+            return userState.inputMap.get(sessionState.inputId);
+        }
+        return null;
+    }
+
     @GuardedBy("mLock")
     private List<TunedInfo> getCurrentTunedInfosInternalLocked(
             UserState userState, int callingPid, int callingUid) {
@@ -2367,6 +2435,28 @@
         }
     }
 
+    /**
+     * Log Tune state changes to {@link FrameworkStatsLog}.
+     *
+     * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+     * Inspect the code at:
+     *
+     * <ul>
+     *   <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+     *   <li>{@link #logTuneStateChanged}
+     *   <li>{@link TvInputManagerService.BinderService#createSession}
+     *   <li>{@link SessionState#sessionId}
+     * </ul>
+     */
+    private void logTuneStateChanged(int state, SessionState sessionState,
+            @Nullable TvInputState inputState) {
+        // TODO(b/173536904): log input type and id
+        FrameworkStatsLog.write(FrameworkStatsLog.TIF_TUNE_CHANGED,
+                new int[]{sessionState.callingUid,
+                        inputState == null ? Process.INVALID_UID : inputState.uid},
+                new String[]{"tif_player", "tv_input_service"}, state, sessionState.sessionId);
+    }
+
     private static final class UserState {
         // A mapping from the TV input id to its TvInputState.
         private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -2464,10 +2554,25 @@
     }
 
     private static final class TvInputState {
-        // A TvInputInfo object which represents the TV input.
+
+        /** A TvInputInfo object which represents the TV input. */
         private TvInputInfo info;
 
-        // The state of TV input. Connected by default.
+        /**
+         * The kernel user-ID that has been assigned to the application the TvInput is a part of.
+         *
+         * <p>
+         * Currently this is not a unique ID (multiple applications can have
+         * the same uid).
+         */
+        private int uid;
+
+        /**
+         * The state of TV input.
+         *
+         * <p>
+         * Connected by default
+         */
         private int state = INPUT_STATE_CONNECTED;
 
         @Override
@@ -2478,6 +2583,23 @@
 
     private final class SessionState implements IBinder.DeathRecipient {
         private final String inputId;
+
+        /**
+         * A randomly generated id for this this session.
+         *
+         * <p>This field contains no user or device reference and is large enough to be
+         * effectively globally unique.
+         *
+         * <p><b>WARNING</b> Any changes to this field should be carefully reviewed for privacy.
+         * Inspect the code at:
+         *
+         * <ul>
+         *   <li>framework/base/cmds/statsd/src/atoms.proto#TifTuneState
+         *   <li>{@link #logTuneStateChanged}
+         *   <li>{@link TvInputManagerService.BinderService#createSession}
+         *   <li>{@link SessionState#sessionId}
+         * </ul>
+         */
         private final String sessionId;
         private final ComponentName componentName;
         private final boolean isRecordingSession;
@@ -2545,7 +2667,7 @@
                 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
             }
             synchronized (mLock) {
-                UserState userState = mUserStates.get(mUserId);
+                UserState userState = getUserStateLocked(mUserId);
                 if (userState == null) {
                     // The user was removed while connecting.
                     mContext.unbindService(this);
@@ -2815,8 +2937,13 @@
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
                 }
+                TvInputState tvInputState = getTvInputState(mSessionState,
+                        getUserStateLocked(mCurrentUserId));
                 try {
                     mSessionState.client.onVideoAvailable(mSessionState.seq);
+                    logTuneStateChanged(
+                            FrameworkStatsLog.TIF_TUNE_STATE_CHANGED__STATE__VIDEO_AVAILABLE,
+                            mSessionState, tvInputState);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "error in onVideoAvailable", e);
                 }
@@ -2832,8 +2959,20 @@
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
                 }
+                TvInputState tvInputState = getTvInputState(mSessionState,
+                        getUserStateLocked(mCurrentUserId));
                 try {
                     mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
+                    int loggedReason = reason + FrameworkStatsLog
+                            .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+                    if (loggedReason < FrameworkStatsLog
+                            .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN
+                            || loggedReason > FrameworkStatsLog
+                            .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN) {
+                        loggedReason = FrameworkStatsLog
+                                .TIF_TUNE_STATE_CHANGED__STATE__VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+                    }
+                    logTuneStateChanged(loggedReason, mSessionState, tvInputState);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "error in onVideoUnavailable", e);
                 }
@@ -3019,6 +3158,10 @@
         }
     }
 
+    private UserState getUserStateLocked(int userId) {
+        return mUserStates.get(userId);
+    }
+
     private static final class WatchLogHandler extends Handler {
         // There are only two kinds of watch events that can happen on the system:
         // 1. The current TV input session is tuned to a new channel.
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
new file mode 100644
index 0000000..e1feb5a
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Handler;
+import android.os.ParcelUuid;
+
+import java.util.Objects;
+
+/**
+ * Tracks a set of Networks underpinning a VcnGatewayConnection.
+ *
+ * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST
+ * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to
+ * be reaped.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkTracker extends Handler {
+    @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final UnderlyingNetworkTrackerCallback mCb;
+    @NonNull private final Dependencies mDeps;
+
+    public UnderlyingNetworkTracker(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull UnderlyingNetworkTrackerCallback cb) {
+        this(vcnContext, subscriptionGroup, cb, new Dependencies());
+    }
+
+    private UnderlyingNetworkTracker(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull UnderlyingNetworkTrackerCallback cb,
+            @NonNull Dependencies deps) {
+        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+        mVcnContext = vcnContext;
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mCb = Objects.requireNonNull(cb, "Missing cb");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+    }
+
+    /** Tears down this Tracker, and releases all underlying network requests. */
+    public void teardown() {}
+
+    /** An record of a single underlying network, caching relevant fields. */
+    public static class UnderlyingNetworkRecord {
+        @NonNull public final Network network;
+        @NonNull public final NetworkCapabilities networkCapabilities;
+        @NonNull public final LinkProperties linkProperties;
+        public final boolean blocked;
+
+        private UnderlyingNetworkRecord(
+                @NonNull Network network,
+                @NonNull NetworkCapabilities networkCapabilities,
+                @NonNull LinkProperties linkProperties,
+                boolean blocked) {
+            this.network = network;
+            this.networkCapabilities = networkCapabilities;
+            this.linkProperties = linkProperties;
+            this.blocked = blocked;
+        }
+    }
+
+    /** Callbacks for being notified of the changes in, or to the selected underlying network. */
+    public interface UnderlyingNetworkTrackerCallback {
+        /**
+         * Fired when a new underlying network is selected, or properties have changed.
+         *
+         * <p>This callback does NOT signal a mobility event.
+         *
+         * @param underlying The details of the new underlying network
+         */
+        void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+    }
+
+    private static class Dependencies {}
+}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
new file mode 100644
index 0000000..d51d16b
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import android.annotation.NonNull;
+import android.net.NetworkRequest;
+import android.net.vcn.VcnConfig;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+
+import java.util.Objects;
+
+/**
+ * Represents an single instance of a VCN.
+ *
+ * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
+ * networks, network selection, and multi-homing.
+ *
+ * @hide
+ */
+public class Vcn extends Handler {
+    private static final String TAG = Vcn.class.getSimpleName();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final Dependencies mDeps;
+
+    @NonNull private VcnConfig mConfig;
+
+    public Vcn(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config) {
+        this(vcnContext, subscriptionGroup, config, new Dependencies());
+    }
+
+    private Vcn(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config,
+            @NonNull Dependencies deps) {
+        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+        mVcnContext = vcnContext;
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mConfig = Objects.requireNonNull(config, "Missing config");
+    }
+
+    /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
+    public void updateConfig(@NonNull VcnConfig config) {
+        Objects.requireNonNull(config, "Missing config");
+        // TODO: Proxy to handler, and make config there.
+    }
+
+    /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */
+    public void teardown() {
+        // TODO: Proxy to handler, and teardown there.
+    }
+
+    /** Notifies this Vcn instance of a new NetworkRequest */
+    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+        Objects.requireNonNull(request, "Missing request");
+
+        // TODO: Proxy to handler, and handle there.
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        // TODO: Do something
+    }
+
+    /** Retrieves the network score for a VCN Network */
+    private int getNetworkScore() {
+        // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in
+        //                               subGrp" value
+        return 52;
+    }
+
+    private static class Dependencies {}
+}
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
new file mode 100644
index 0000000..8ab52931
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+
+import com.android.server.VcnManagementService.VcnNetworkProvider;
+
+import java.util.Objects;
+
+/**
+ * A simple class to pass around context information.
+ *
+ * @hide
+ */
+public class VcnContext {
+    @NonNull private final Context mContext;
+    @NonNull private final Looper mLooper;
+    @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
+
+    public VcnContext(
+            @NonNull Context context,
+            @NonNull Looper looper,
+            @NonNull VcnNetworkProvider vcnNetworkProvider) {
+        mContext = Objects.requireNonNull(context, "Missing context");
+        mLooper = Objects.requireNonNull(looper, "Missing looper");
+        mVcnNetworkProvider = Objects.requireNonNull(vcnNetworkProvider, "Missing networkProvider");
+    }
+
+    @NonNull
+    public Context getContext() {
+        return mContext;
+    }
+
+    @NonNull
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    @NonNull
+    public VcnNetworkProvider getVcnNetworkProvider() {
+        return mVcnNetworkProvider;
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
new file mode 100644
index 0000000..49c9b32
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Handler;
+import android.os.ParcelUuid;
+
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import java.util.Objects;
+
+/**
+ * A single VCN Gateway Connection, providing a single public-facing VCN network.
+ *
+ * <p>This class handles mobility events, performs retries, and tracks safe-mode conditions.
+ *
+ * @hide
+ */
+public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTrackerCallback {
+    private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
+    @NonNull private final Dependencies mDeps;
+
+    public VcnGatewayConnection(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnGatewayConnectionConfig connectionConfig) {
+        this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies());
+    }
+
+    private VcnGatewayConnection(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull Dependencies deps) {
+        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+        mVcnContext = vcnContext;
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mUnderlyingNetworkTracker =
+                mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
+    }
+
+    /** Tears down this GatewayConnection, and any resources used */
+    public void teardown() {
+        mUnderlyingNetworkTracker.teardown();
+    }
+
+    private static class Dependencies {
+        public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
+                VcnContext vcnContext,
+                ParcelUuid subscriptionGroup,
+                UnderlyingNetworkTrackerCallback callback) {
+            return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+        }
+    }
+
+    @Override
+    public void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying) {}
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d8d1a65..b4ca7c5 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -3,8 +3,10 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.processStateAmToProto;
+import static android.app.WaitResult.INVALID_DELAY;
 import static android.app.WaitResult.LAUNCH_STATE_COLD;
 import static android.app.WaitResult.LAUNCH_STATE_HOT;
+import static android.app.WaitResult.LAUNCH_STATE_RELAUNCH;
 import static android.app.WaitResult.LAUNCH_STATE_WARM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -130,7 +132,6 @@
      * transition, in the case the launch is standalone (e.g. from recents).
      */
     private static final int IGNORE_CALLER = -1;
-    private static final int INVALID_DELAY = -1;
 
     // Preallocated strings we are sending to tron, so we don't have to allocate a new one every
     // time we log.
@@ -220,6 +221,8 @@
         boolean mLoggedStartingWindowDrawn;
         /** If the any app transitions have been logged as starting. */
         boolean mLoggedTransitionStarting;
+        /** Whether any activity belonging to this transition has relaunched. */
+        boolean mRelaunched;
 
         /** Non-null if the application has reported drawn but its window hasn't. */
         @Nullable Runnable mPendingFullyDrawn;
@@ -351,6 +354,7 @@
          */
         final int windowsFullyDrawnDelayMs;
         final int activityRecordIdHashCode;
+        final boolean relaunched;
 
         private TransitionInfoSnapshot(TransitionInfo info) {
             this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -379,6 +383,7 @@
             launchedActivityShortComponentName = launchedActivity.shortComponentName;
             activityRecordIdHashCode = System.identityHashCode(launchedActivity);
             this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
+            relaunched = info.mRelaunched;
         }
 
         @WaitResult.LaunchState int getLaunchState() {
@@ -386,7 +391,7 @@
                 case TYPE_TRANSITION_WARM_LAUNCH:
                     return LAUNCH_STATE_WARM;
                 case TYPE_TRANSITION_HOT_LAUNCH:
-                    return LAUNCH_STATE_HOT;
+                    return relaunched ? LAUNCH_STATE_RELAUNCH : LAUNCH_STATE_HOT;
                 case TYPE_TRANSITION_COLD_LAUNCH:
                     return LAUNCH_STATE_COLD;
                 default:
@@ -673,6 +678,13 @@
         }
     }
 
+    void notifyActivityRelaunched(ActivityRecord r) {
+        final TransitionInfo info = getActiveTransitionInfo(r);
+        if (info != null) {
+            info.mRelaunched = true;
+        }
+    }
+
     /** Makes sure that the reference to the removed activity is cleared. */
     void notifyActivityRemoved(@NonNull ActivityRecord r) {
         mLastTransitionInfo.remove(r);
@@ -800,13 +812,13 @@
                 FrameworkStatsLog.APP_START_CANCELED,
                 activity.info.applicationInfo.uid,
                 activity.packageName,
-                convertAppStartTransitionType(type),
+                getAppStartTransitionType(type, info.mRelaunched),
                 activity.info.name);
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_CANCELED(%s, %s, %s, %s)",
                     activity.info.applicationInfo.uid,
                     activity.packageName,
-                    convertAppStartTransitionType(type),
+                    getAppStartTransitionType(type, info.mRelaunched),
                     activity.info.name));
         }
     }
@@ -871,7 +883,7 @@
                 FrameworkStatsLog.APP_START_OCCURRED,
                 info.applicationInfo.uid,
                 info.packageName,
-                convertAppStartTransitionType(info.type),
+                getAppStartTransitionType(info.type, info.relaunched),
                 info.launchedActivityName,
                 info.launchedActivityLaunchedFromPackage,
                 isInstantApp,
@@ -891,7 +903,7 @@
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
                     info.applicationInfo.uid,
                     info.packageName,
-                    convertAppStartTransitionType(info.type),
+                    getAppStartTransitionType(info.type, info.relaunched),
                     info.launchedActivityName,
                     info.launchedActivityLaunchedFromPackage));
         }
@@ -918,7 +930,7 @@
         Log.i(TAG, sb.toString());
     }
 
-    private int convertAppStartTransitionType(int tronType) {
+    private static int getAppStartTransitionType(int tronType, boolean relaunched) {
         if (tronType == TYPE_TRANSITION_COLD_LAUNCH) {
             return FrameworkStatsLog.APP_START_OCCURRED__TYPE__COLD;
         }
@@ -926,17 +938,13 @@
             return FrameworkStatsLog.APP_START_OCCURRED__TYPE__WARM;
         }
         if (tronType == TYPE_TRANSITION_HOT_LAUNCH) {
-            return FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT;
+            return relaunched
+                    ? FrameworkStatsLog.APP_START_OCCURRED__TYPE__RELAUNCH
+                    : FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT;
         }
         return FrameworkStatsLog.APP_START_OCCURRED__TYPE__UNKNOWN;
     }
 
-    /** @return the last known window drawn delay of the given activity. */
-    int getLastDrawnDelayMs(ActivityRecord r) {
-        final TransitionInfo info = mLastTransitionInfo.get(r);
-        return info != null ? info.mWindowsDrawnDelayMs : INVALID_DELAY;
-    }
-
     /** @see android.app.Activity#reportFullyDrawn */
     TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
             boolean restoredFromBundle) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3591779..c2016de 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,7 +226,7 @@
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
-import android.app.WaitResult.LaunchState;
+import android.app.WaitResult;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -1558,10 +1558,6 @@
             }
         }
 
-        // Application tokens start out hidden.
-        setVisible(false);
-        mVisibleRequested = false;
-
         ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
                 ColorDisplayService.ColorDisplayServiceInternal.class);
         cds.attachColorTransformController(packageName, mUserId,
@@ -3130,6 +3126,7 @@
     }
 
     void finishRelaunching() {
+        mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
         unfreezeBounds();
 
         if (mPendingRelaunchCount > 0) {
@@ -3506,7 +3503,7 @@
                 }
                 if (fromActivity.isVisible()) {
                     setVisible(true);
-                    mVisibleRequested = true;
+                    setVisibleRequested(true);
                     mVisibleSetFromTransferredStartingWindow = true;
                 }
                 setClientVisible(fromActivity.mClientVisible);
@@ -4122,11 +4119,28 @@
     void setVisible(boolean visible) {
         if (visible != mVisible) {
             mVisible = visible;
+            if (app != null) {
+                mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
+            }
             scheduleAnimation();
         }
     }
 
     /**
+     * This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
+     * outside of this class should use {@link #setVisibility}.
+     */
+    private void setVisibleRequested(boolean visible) {
+        if (visible == mVisibleRequested) {
+            return;
+        }
+        mVisibleRequested = visible;
+        if (app != null) {
+            mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
+        }
+    }
+
+    /**
      * Set visibility on this {@link ActivityRecord}
      *
      * <p class="note"><strong>Note: </strong>This function might not update the visibility of
@@ -4189,7 +4203,7 @@
         displayContent.mOpeningApps.remove(this);
         displayContent.mClosingApps.remove(this);
         waitingToShow = false;
-        mVisibleRequested = visible;
+        setVisibleRequested(visible);
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
@@ -4331,7 +4345,7 @@
                     ANIMATION_TYPE_APP_TRANSITION));
         }
         setVisible(visible);
-        mVisibleRequested = visible;
+        setVisibleRequested(visible);
         if (!visible) {
             stopFreezingScreen(true, true);
         } else {
@@ -4519,7 +4533,7 @@
             detachChildren();
         }
         if (app != null) {
-            app.computeProcessActivityState();
+            mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
 
         switch (state) {
@@ -5435,14 +5449,15 @@
                 .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
         final boolean validInfo = info != null;
         final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY;
-        final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1;
+        final @WaitResult.LaunchState int launchState =
+                validInfo ? info.getLaunchState() : WaitResult.LAUNCH_STATE_UNKNOWN;
         // The activity may have been requested to be invisible (another activity has been launched)
         // so there is no valid info. But if it is the current top activity (e.g. sleeping), the
         // invalid state is still reported to make sure the waiting result is notified.
         if (validInfo || this == getDisplayArea().topRunningActivity()) {
             mTaskSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
                     windowsDrawnDelayMs, launchState);
-            mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+            mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs, launchState);
         }
         finishLaunchTickingLocked();
         if (task != null) {
@@ -6393,13 +6408,10 @@
             // The app is just becoming visible, and the parent Task has updated with the
             // orientation request. Update the size compat mode.
             updateSizeCompatMode();
-            if (task.isOrganized()) {
-                // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure
-                // that WM Shell is called when an activity becomes visible. Without this, WM Core
-                // will handle positioning instead of WM Shell when an app is reopened.
-                mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                        task, /* force= */ true);
-            }
+            // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure
+            // that WM Shell is called when an activity becomes visible. Without this, WM Core
+            // will handle positioning instead of WM Shell when an app is reopened.
+            task.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
         }
     }
 
@@ -7163,11 +7175,7 @@
             } else {
                 mRelaunchReason = RELAUNCH_REASON_NONE;
             }
-            if (!attachedToProcess()) {
-                ProtoLog.v(WM_DEBUG_CONFIGURATION,
-                        "Config is destroying non-running %s", this);
-                destroyImmediately("config");
-            } else if (mState == PAUSING) {
+            if (mState == PAUSING) {
                 // A little annoying: we are waiting for this activity to finish pausing. Let's not
                 // do anything now, but just flag that it needs to be restarted when done pausing.
                 ProtoLog.v(WM_DEBUG_CONFIGURATION,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f472a5d..acee7b2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -28,8 +28,6 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WaitResult.LAUNCH_STATE_COLD;
-import static android.app.WaitResult.LAUNCH_STATE_HOT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -819,8 +817,6 @@
                 break;
             }
             case START_TASK_TO_FRONT: {
-                mRequest.waitResult.launchState =
-                        r.attachedToProcess() ? LAUNCH_STATE_HOT : LAUNCH_STATE_COLD;
                 // ActivityRecord may represent a different activity, but it should not be
                 // in the resumed state.
                 if (r.nowVisible && r.isState(RESUMED)) {
@@ -1285,6 +1281,15 @@
             return false;
         }
 
+        // IME should always be allowed to start activity, like IME settings.
+        final WindowState imeWindow = mRootWindowContainer.getCurrentInputMethodWindow();
+        if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "Activity start allowed for active ime (" + callingUid + ")");
+            }
+            return false;
+        }
+
         // App switching will be allowed if BAL app switching flag is not enabled, or if
         // its app switching rule allows it.
         // This is used to block background activity launch even if the app is still
@@ -1292,9 +1297,8 @@
         final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
 
         // don't abort if the callingUid has a visible window or is a persistent system process
-        final int callingUidProcState = mService.getUidState(callingUid);
-        final boolean callingUidHasAnyVisibleWindow =
-                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
+        final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
+        final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
         final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
                 || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                 || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
@@ -1312,10 +1316,10 @@
         // take realCallingUid into consideration
         final int realCallingUidProcState = (callingUid == realCallingUid)
                 ? callingUidProcState
-                : mService.getUidState(realCallingUid);
+                : mService.mActiveUids.getUidState(realCallingUid);
         final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
                 ? callingUidHasAnyVisibleWindow
-                : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
+                : mService.hasActiveVisibleWindow(realCallingUid);
         final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
                 ? isCallingUidForeground
                 : realCallingUidHasAnyVisibleWindow
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index f649a2f..a5df2a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -514,7 +514,6 @@
 
     public abstract void onUidActive(int uid, int procState);
     public abstract void onUidInactive(int uid);
-    public abstract void onActiveUidsCleared();
     public abstract void onUidProcStateChanged(int uid, int procState);
 
     public abstract void onUidAddedToPendingTempAllowlist(int uid, String tag);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4c607d..ad6b505 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,7 +390,7 @@
     private UserManagerService mUserManager;
     private AppOpsManager mAppOpsManager;
     /** All active uids in the system. */
-    private final MirrorActiveUids mActiveUids = new MirrorActiveUids();
+    final MirrorActiveUids mActiveUids = new MirrorActiveUids();
     private final SparseArray<String> mPendingTempAllowlist = new SparseArray<>();
     /** All processes currently running that might have a window organized by name. */
     final ProcessMap<WindowProcessController> mProcessNames = new ProcessMap<>();
@@ -4956,6 +4956,8 @@
             printedAnything = true;
             mTaskSupervisor.dump(pw, "  ");
             mTaskOrganizerController.dump(pw, "  ");
+            mVisibleActivityProcessTracker.dump(pw, "  ");
+            mActiveUids.dump(pw, "  ");
         }
 
         if (!printedAnything) {
@@ -5986,13 +5988,12 @@
         return null;
     }
 
-    int getUidState(int uid) {
-        return mActiveUids.getUidState(uid);
-    }
-
-    boolean isUidForeground(int uid) {
-        // A uid is considered to be foreground if it has a visible non-toast window.
-        return mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
+    /** A uid is considered to be foreground if it has a visible non-toast window. */
+    boolean hasActiveVisibleWindow(int uid) {
+        if (mVisibleActivityProcessTracker.hasVisibleActivity(uid)) {
+            return true;
+        }
+        return mActiveUids.hasNonAppVisibleWindow(uid);
     }
 
     boolean isDeviceOwner(int uid) {
@@ -7283,12 +7284,6 @@
 
         @HotPath(caller = HotPath.OOM_ADJUSTMENT)
         @Override
-        public void onActiveUidsCleared() {
-            mActiveUids.onActiveUidsCleared();
-        }
-
-        @HotPath(caller = HotPath.OOM_ADJUSTMENT)
-        @Override
         public void onUidProcStateChanged(int uid, int procState) {
             mActiveUids.onUidProcStateChanged(uid, procState);
         }
@@ -7432,9 +7427,7 @@
 
         @Override
         public boolean isUidForeground(int uid) {
-            synchronized (mGlobalLock) {
-                return ActivityTaskManagerService.this.isUidForeground(uid);
-            }
+            return ActivityTaskManagerService.this.hasActiveVisibleWindow(uid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e91a6d8..37f04ce 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -254,6 +254,12 @@
     private LaunchParamsController mLaunchParamsController;
 
     /**
+     * The processes with changed states that should eventually call
+     * {@link WindowProcessController#computeProcessActivityState}.
+     */
+    private final ArrayList<WindowProcessController> mActivityStateChangedProcs = new ArrayList<>();
+
+    /**
      * Maps the task identifier that activities are currently being started in to the userId of the
      * task. Each time a new task is created, the entry for the userId of the task is incremented
      */
@@ -553,14 +559,16 @@
         // down to the max limit while they are still waiting to finish.
         mFinishingActivities.remove(r);
 
-        stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY);
+        stopWaitingForActivityVisible(r);
     }
 
+    /** There is no valid launch time, just stop waiting. */
     void stopWaitingForActivityVisible(ActivityRecord r) {
-        stopWaitingForActivityVisible(r, getActivityMetricsLogger().getLastDrawnDelayMs(r));
+        stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY, WaitResult.LAUNCH_STATE_UNKNOWN);
     }
 
-    void stopWaitingForActivityVisible(ActivityRecord r, long totalTime) {
+    void stopWaitingForActivityVisible(ActivityRecord r, long totalTime,
+            @WaitResult.LaunchState int launchState) {
         boolean changed = false;
         for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) {
             final WaitInfo w = mWaitingForActivityVisible.get(i);
@@ -570,6 +578,7 @@
                 result.timeout = false;
                 result.who = w.getComponent();
                 result.totalTime = totalTime;
+                result.launchState = launchState;
                 mWaitingForActivityVisible.remove(w);
             }
         }
@@ -2307,6 +2316,9 @@
     /** Ends a batch of visibility updates. */
     void endActivityVisibilityUpdate() {
         mVisibilityTransactionDepth--;
+        if (mVisibilityTransactionDepth == 0) {
+            computeProcessActivityStateBatch();
+        }
     }
 
     /** Returns {@code true} if the caller is on the path to update visibility. */
@@ -2315,6 +2327,34 @@
     }
 
     /**
+     * Called when the state or visibility of an attached activity is changed.
+     *
+     * @param wpc The process who owns the activity.
+     * @param forceBatch Whether to put the changed record to a pending list. If the caller is not
+     *                   in the path of visibility update ({@link #inActivityVisibilityUpdate}), it
+     *                   must call {@link #computeProcessActivityStateBatch} manually.
+     */
+    void onProcessActivityStateChanged(WindowProcessController wpc, boolean forceBatch) {
+        if (forceBatch || inActivityVisibilityUpdate()) {
+            if (!mActivityStateChangedProcs.contains(wpc)) {
+                mActivityStateChangedProcs.add(wpc);
+            }
+            return;
+        }
+        wpc.computeProcessActivityState();
+    }
+
+    void computeProcessActivityStateBatch() {
+        if (mActivityStateChangedProcs.isEmpty()) {
+            return;
+        }
+        for (int i = mActivityStateChangedProcs.size() - 1; i >= 0; i--) {
+            mActivityStateChangedProcs.get(i).computeProcessActivityState();
+        }
+        mActivityStateChangedProcs.clear();
+    }
+
+    /**
      * Begin deferring resume to avoid duplicate resumes in one pass.
      */
     void beginDeferResume() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ec95089..4133ea2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -626,12 +626,6 @@
      */
     private boolean mInEnsureActivitiesVisible = false;
 
-    /**
-     * Last window to be requested focus via {@code SurfaceControl.Transaction#setFocusedWindow} to
-     * prevent duplicate requests to input.
-     */
-    WindowState mLastRequestedFocus = null;
-
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -5384,20 +5378,6 @@
         forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
     }
 
-    /**
-     * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but
-     * used for pid.
-     */
-    boolean isAnyNonToastWindowVisibleForPid(int pid) {
-        final PooledPredicate p = PooledLambda.obtainPredicate(
-                WindowState::isNonToastWindowVisibleForPid,
-                PooledLambda.__(WindowState.class), pid);
-
-        final WindowState w = getWindow(p);
-        p.recycle();
-        return w != null;
-    }
-
     Context getDisplayUiContext() {
         return mDisplayPolicy.getSystemUiContext();
     }
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
new file mode 100644
index 0000000..4793e1b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.attestation.IImpressionAttestationService;
+import android.service.attestation.ImpressionAttestationService;
+import android.service.attestation.ImpressionToken;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+
+/**
+ * Handles requests into {@link ImpressionAttestationService}
+ *
+ * Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are
+ * blocking calls into another service.
+ */
+public class ImpressionAttestationController {
+    private static final String TAG = "ImpressionAttestationController";
+    private static final boolean DEBUG = false;
+
+    private final Object mServiceConnectionLock = new Object();
+
+    @GuardedBy("mServiceConnectionLock")
+    private ImpressionAttestationServiceConnection mServiceConnection;
+
+    private final Context mContext;
+
+    /**
+     * Lock used for the cached {@link #mImpressionAlgorithms} array
+     */
+    private final Object mImpressionAlgorithmsLock = new Object();
+
+    @GuardedBy("mImpressionAlgorithmsLock")
+    private String[] mImpressionAlgorithms;
+
+    private final Handler mHandler;
+
+    private final String mSalt;
+
+    private interface Command {
+        void run(IImpressionAttestationService service) throws RemoteException;
+    }
+
+    ImpressionAttestationController(Context context) {
+        mContext = context;
+        mHandler = new Handler(Looper.getMainLooper());
+        mSalt = UUID.randomUUID().toString();
+    }
+
+    String[] getSupportedImpressionAlgorithms() {
+        // We have a separate lock for the impression algorithm array since it doesn't need to make
+        // the request through the service connection. Instead, we have a lock to ensure we can
+        // properly cache the impression algorithms array so we don't need to call into the
+        // ExtServices process for each request.
+        synchronized (mImpressionAlgorithmsLock) {
+            // Already have cached values
+            if (mImpressionAlgorithms != null) {
+                return mImpressionAlgorithms;
+            }
+
+            final ServiceInfo serviceInfo = getServiceInfo();
+            if (serviceInfo == null) return null;
+
+            final PackageManager pm = mContext.getPackageManager();
+            final Resources res;
+            try {
+                res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
+                return null;
+            }
+
+            final int resourceId = serviceInfo.metaData.getInt(
+                    SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
+            mImpressionAlgorithms = res.getStringArray(resourceId);
+
+            return mImpressionAlgorithms;
+        }
+    }
+
+    boolean verifyImpressionToken(ImpressionToken impressionToken) {
+        final SyncCommand syncCommand = new SyncCommand();
+        Bundle results = syncCommand.run((service, remoteCallback) -> {
+            try {
+                service.verifyImpressionToken(mSalt, impressionToken, remoteCallback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to invoke verifyImpressionToken command");
+            }
+        });
+
+        return results.getBoolean(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS);
+    }
+
+    ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+            String hashAlgorithm) {
+        final SyncCommand syncCommand = new SyncCommand();
+        Bundle results = syncCommand.run((service, remoteCallback) -> {
+            try {
+                service.generateImpressionToken(mSalt, screenshot, bounds, hashAlgorithm,
+                        remoteCallback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to invoke generateImpressionToken command", e);
+            }
+        });
+
+        return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN);
+    }
+
+    /**
+     * Run a command, starting the service connection if necessary.
+     */
+    private void connectAndRun(@NonNull Command command) {
+        synchronized (mServiceConnectionLock) {
+            mHandler.resetTimeoutMessage();
+            if (mServiceConnection == null) {
+                if (DEBUG) Slog.v(TAG, "creating connection");
+
+                // Create the connection
+                mServiceConnection = new ImpressionAttestationServiceConnection();
+
+                final ComponentName component = getServiceComponentName();
+                if (DEBUG) Slog.v(TAG, "binding to: " + component);
+                if (component != null) {
+                    final Intent intent = new Intent();
+                    intent.setComponent(component);
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mContext.bindServiceAsUser(intent, mServiceConnection,
+                                Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+                        if (DEBUG) Slog.v(TAG, "bound");
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            }
+
+            mServiceConnection.runCommandLocked(command);
+        }
+    }
+
+    @Nullable
+    private ServiceInfo getServiceInfo() {
+        final String packageName =
+                mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+        if (packageName == null) {
+            Slog.w(TAG, "no external services package!");
+            return null;
+        }
+
+        final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+        final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            Slog.w(TAG, "No valid components found.");
+            return null;
+        }
+        return resolveInfo.serviceInfo;
+    }
+
+    @Nullable
+    private ComponentName getServiceComponentName() {
+        final ServiceInfo serviceInfo = getServiceInfo();
+        if (serviceInfo == null) return null;
+
+        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE
+                .equals(serviceInfo.permission)) {
+            Slog.w(TAG, name.flattenToShortString() + " requires permission "
+                    + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE);
+            return null;
+        }
+
+        if (DEBUG) Slog.v(TAG, "getServiceComponentName(): " + name);
+        return name;
+    }
+
+    private class SyncCommand {
+        private static final int WAIT_TIME_S = 5;
+        private Bundle mResult;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) {
+            connectAndRun(service -> {
+                RemoteCallback callback = new RemoteCallback(result -> {
+                    mResult = result;
+                    mCountDownLatch.countDown();
+                });
+                func.accept(service, callback);
+            });
+
+            try {
+                mCountDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to wait for command", e);
+            }
+
+            return mResult;
+        }
+    }
+
+    private class ImpressionAttestationServiceConnection implements ServiceConnection {
+        @GuardedBy("mServiceConnectionLock")
+        private IImpressionAttestationService mRemoteService;
+
+        @GuardedBy("mServiceConnectionLock")
+        private ArrayList<Command> mQueuedCommands;
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name);
+            synchronized (mServiceConnectionLock) {
+                mRemoteService = IImpressionAttestationService.Stub.asInterface(service);
+                if (mQueuedCommands != null) {
+                    final int size = mQueuedCommands.size();
+                    if (DEBUG) Slog.d(TAG, "running " + size + " queued commands");
+                    for (int i = 0; i < size; i++) {
+                        final Command queuedCommand = mQueuedCommands.get(i);
+                        try {
+                            if (DEBUG) Slog.v(TAG, "running queued command #" + i);
+                            queuedCommand.run(mRemoteService);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "exception calling " + name + ": " + e);
+                        }
+                    }
+                    mQueuedCommands = null;
+                } else if (DEBUG) {
+                    Slog.d(TAG, "no queued commands");
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG) Slog.v(TAG, "onServiceDisconnected(): " + name);
+            synchronized (mServiceConnectionLock) {
+                mRemoteService = null;
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            if (DEBUG) Slog.v(TAG, "onBindingDied(): " + name);
+            synchronized (mServiceConnectionLock) {
+                mRemoteService = null;
+            }
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            if (DEBUG) Slog.v(TAG, "onNullBinding(): " + name);
+            synchronized (mServiceConnectionLock) {
+                mRemoteService = null;
+            }
+        }
+
+        /**
+         * Only call while holding {@link #mServiceConnectionLock}
+         */
+        private void runCommandLocked(Command command) {
+            if (mRemoteService == null) {
+                if (DEBUG) Slog.d(TAG, "service is null; queuing command");
+                if (mQueuedCommands == null) {
+                    mQueuedCommands = new ArrayList<>(1);
+                }
+                mQueuedCommands.add(command);
+            } else {
+                try {
+                    if (DEBUG) Slog.v(TAG, "running command right away");
+                    command.run(mRemoteService);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "exception calling service: " + e);
+                }
+            }
+        }
+    }
+
+    private class Handler extends android.os.Handler {
+        static final long SERVICE_SHUTDOWN_TIMEOUT_MILLIS = 10000; // 10s
+        static final int MSG_SERVICE_SHUTDOWN_TIMEOUT = 1;
+
+        Handler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_SERVICE_SHUTDOWN_TIMEOUT) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Shutting down service");
+                }
+                synchronized (mServiceConnectionLock) {
+                    if (mServiceConnection != null) {
+                        mContext.unbindService(mServiceConnection);
+                        mServiceConnection = null;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Set a timer for {@link #SERVICE_SHUTDOWN_TIMEOUT_MILLIS} so we can tear down the service
+         * if it's inactive. The requests will be coming from apps so it's hard to tell how often
+         * the requests can come in. Therefore, we leave the service running if requests continue
+         * to come in. Once there's been no activity for 10s, we can shut down the service and
+         * restart when we get a new request.
+         */
+        void resetTimeoutMessage() {
+            if (DEBUG) {
+                Slog.v(TAG, "Reset shutdown message");
+            }
+            removeMessages(MSG_SERVICE_SHUTDOWN_TIMEOUT);
+            sendEmptyMessageDelayed(MSG_SERVICE_SHUTDOWN_TIMEOUT, SERVICE_SHUTDOWN_TIMEOUT_MILLIS);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 457df4e..efd9e2a 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -73,8 +73,8 @@
 final class InputMonitor {
     private final WindowManagerService mService;
 
-    // Current window with input focus for keys and other non-touch events.  May be null.
-    private WindowState mInputFocus;
+    // Current input focus token for keys and other non-touch events.  May be null.
+    private IBinder mInputFocus = null;
 
     // When true, need to call updateInputWindowsLw().
     private boolean mUpdateInputWindowsNeeded = true;
@@ -377,31 +377,62 @@
     }
 
     /**
-     * Called when the current input focus changes.
+     * Called when the current input focus changes. Will apply it in next updateInputWindows.
      * Layer assignment is assumed to be complete by the time this is called.
      */
-    public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
+    void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
         ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",
                 newWindow, mDisplayId);
+        final IBinder focus = newWindow != null ? newWindow.mInputChannelToken : null;
+        if (focus == mInputFocus) {
+            return;
+        }
 
-        if (newWindow != mInputFocus) {
-            if (newWindow != null && newWindow.canReceiveKeys()) {
-                // Displaying a window implicitly causes dispatching to be unpaused.
-                // This is to protect against bugs if someone pauses dispatching but
-                // forgets to resume.
-                newWindow.mToken.paused = false;
-            }
+        if (newWindow != null && newWindow.canReceiveKeys()) {
+            // Displaying a window implicitly causes dispatching to be unpaused.
+            // This is to protect against bugs if someone pauses dispatching but
+            // forgets to resume.
+            newWindow.mToken.paused = false;
+        }
 
-            mInputFocus = newWindow;
-            setUpdateInputWindowsNeededLw();
+        setUpdateInputWindowsNeededLw();
 
-            if (updateInputWindows) {
-                updateInputWindowsLw(false /*force*/);
-            }
+        if (updateInputWindows) {
+            updateInputWindowsLw(false /*force*/);
         }
     }
 
-    public void setFocusedAppLw(ActivityRecord newApp) {
+    /**
+     * Called when the current input focus changes.
+     */
+    private void updateInputFocusRequest() {
+        final WindowState focus = mDisplayContent.mCurrentFocus;
+        final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;
+
+        if (focusToken == null) {
+            mInputFocus = null;
+            return;
+        }
+
+        if (!focus.mWinAnimator.hasSurface() || !focus.mInputWindowHandle.isFocusable()) {
+            Slog.v(TAG_WM, "Focus not requested for window=%" + focus
+                    + " because it has no surface or is not focusable.");
+            mInputFocus = null;
+            return;
+        }
+
+        if (focusToken == mInputFocus) {
+            return;
+        }
+
+        mInputFocus = focusToken;
+        mInputTransaction.setFocusedWindow(mInputFocus, mDisplayId);
+        EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + focus,
+                "reason=UpdateInputWindows");
+        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
+    }
+
+    void setFocusedAppLw(ActivityRecord newApp) {
         // Focused app has changed.
         mService.mInputManager.setFocusedApplication(mDisplayId,
                 newApp != null ? newApp.getInputApplicationHandle(true /* update */) : null);
@@ -482,30 +513,6 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
-        private void updateInputFocusRequest() {
-            if (mDisplayContent.mLastRequestedFocus == mDisplayContent.mCurrentFocus) {
-                return;
-            }
-
-            final WindowState focus = mDisplayContent.mCurrentFocus;
-            if (focus == null || focus.mInputChannelToken == null) {
-                mDisplayContent.mLastRequestedFocus = focus;
-                return;
-            }
-
-            if (!focus.mWinAnimator.hasSurface()) {
-                Slog.v(TAG_WM, "Focus not requested for window=%" + focus
-                        + " because it has no surface.");
-                return;
-            }
-
-            mInputTransaction.setFocusedWindow(focus.mInputChannelToken, mDisplayId);
-            EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
-                    "Focus request " + focus, "reason=UpdateInputWindows");
-            mDisplayContent.mLastRequestedFocus = focus;
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
-        }
-
         @Override
         public void accept(WindowState w) {
             final InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 9339f34..7a4d13c 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -63,6 +63,10 @@
         return mHandle.displayId;
     }
 
+    boolean isFocusable() {
+        return mHandle.focusable;
+    }
+
     InputApplicationHandle getInputApplicationHandle() {
         return mHandle.inputApplicationHandle;
     }
diff --git a/services/core/java/com/android/server/wm/MirrorActiveUids.java b/services/core/java/com/android/server/wm/MirrorActiveUids.java
index 0047942..4e7f1d4 100644
--- a/services/core/java/com/android/server/wm/MirrorActiveUids.java
+++ b/services/core/java/com/android/server/wm/MirrorActiveUids.java
@@ -18,7 +18,10 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 
-import android.util.SparseIntArray;
+import android.app.ActivityManager.ProcessState;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
 
 /**
  * This is a partial mirror of {@link @com.android.server.am.ActiveUids}. It is already thread
@@ -26,28 +29,64 @@
  * adjustment) or getting state from window manager (background start check).
  */
 class MirrorActiveUids {
-    private SparseIntArray mUidStates = new SparseIntArray();
+    private final SparseArray<UidRecord> mUidStates = new SparseArray<>();
 
     synchronized void onUidActive(int uid, int procState) {
-        mUidStates.put(uid, procState);
+        UidRecord r = mUidStates.get(uid);
+        if (r == null) {
+            r = new UidRecord();
+            mUidStates.put(uid, r);
+        }
+        r.mProcState = procState;
     }
 
     synchronized void onUidInactive(int uid) {
         mUidStates.delete(uid);
     }
 
-    synchronized void onActiveUidsCleared() {
-        mUidStates.clear();
-    }
-
     synchronized void onUidProcStateChanged(int uid, int procState) {
-        final int index = mUidStates.indexOfKey(uid);
-        if (index >= 0) {
-            mUidStates.setValueAt(index, procState);
+        final UidRecord r = mUidStates.get(uid);
+        if (r != null) {
+            r.mProcState = procState;
         }
     }
 
-    synchronized int getUidState(int uid) {
-        return mUidStates.get(uid, PROCESS_STATE_NONEXISTENT);
+    synchronized @ProcessState int getUidState(int uid) {
+        final UidRecord r = mUidStates.get(uid);
+        return r != null ? r.mProcState : PROCESS_STATE_NONEXISTENT;
+    }
+
+    /** Called when the surface of non-application (exclude toast) window is shown or hidden. */
+    synchronized void onNonAppSurfaceVisibilityChanged(int uid, boolean visible) {
+        final UidRecord r = mUidStates.get(uid);
+        if (r != null) {
+            r.mNumNonAppVisibleWindow += visible ? 1 : -1;
+        }
+    }
+
+    /**
+     * Returns {@code true} if the uid has any non-application (exclude toast) window currently
+     * visible to the user. The application window visibility of a uid can be found from
+     * {@link VisibleActivityProcessTracker}.
+     */
+    synchronized boolean hasNonAppVisibleWindow(int uid) {
+        final UidRecord r = mUidStates.get(uid);
+        return r != null && r.mNumNonAppVisibleWindow > 0;
+    }
+
+    synchronized void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix + "NumNonAppVisibleWindowByUid:[");
+        for (int i = mUidStates.size() - 1; i >= 0; i--) {
+            final UidRecord r = mUidStates.valueAt(i);
+            if (r.mNumNonAppVisibleWindow > 0) {
+                pw.print(" " + mUidStates.keyAt(i) + ":" + r.mNumNonAppVisibleWindow);
+            }
+        }
+        pw.println("]");
+    }
+
+    private static final class UidRecord {
+        @ProcessState int mProcState;
+        int mNumNonAppVisibleWindow;
     }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b117699..823dc51 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -385,12 +385,9 @@
                     mWindowManager.executeAppTransition();
 
                     final Task rootTask = targetStack.getRootTask();
-                    if (rootTask.isOrganized()) {
-                        // Client state may have changed during the recents animation, so force
-                        // send task info so the client can synchronize its state.
-                        mService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                                rootTask, true /* force */);
-                    }
+                    // Client state may have changed during the recents animation, so force
+                    // send task info so the client can synchronize its state.
+                    rootTask.dispatchTaskInfoChangedIfNeeded(true /* force */);
                 } catch (Exception e) {
                     Slog.e(TAG, "Failed to clean up recents activity", e);
                     throw e;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a550e15..7073548 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -276,7 +276,6 @@
     // Whether tasks have moved and we need to rank the tasks before next OOM scoring
     private boolean mTaskLayersChanged = true;
     private int mTmpTaskLayerRank;
-    private final ArraySet<WindowProcessController> mTmpTaskLayerChangedProcs = new ArraySet<>();
     private final LockedScheduler mRankTaskLayersScheduler;
 
     private boolean mTmpBoolean;
@@ -578,23 +577,6 @@
     }
 
     /**
-     * Returns {@code true} if the callingUid has any non-toast window currently visible to the
-     * user. Also ignores {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
-     * since those windows don't belong to apps.
-     *
-     * @see WindowState#isNonToastOrStarting()
-     */
-    boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
-        final PooledPredicate p = PooledLambda.obtainPredicate(
-                WindowState::isNonToastWindowVisibleForUid,
-                PooledLambda.__(WindowState.class), callingUid);
-
-        final WindowState w = getWindow(p);
-        p.recycle();
-        return w != null;
-    }
-
-    /**
      * Returns the app window token for the input binder if it exist in the system.
      * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
      * AppWindowToken represents an activity which can only exist on one display.
@@ -1016,9 +998,6 @@
             }
         }
 
-        // Remove all deferred displays stacks, tasks, and activities.
-        handleCompleteDeferredRemoval();
-
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
@@ -1367,7 +1346,8 @@
         }
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             DisplayContent dc = mChildren.get(i);
-            if (dc.isAnyNonToastWindowVisibleForPid(pid)) {
+            if (dc.getWindow(w -> pid == w.mSession.mPid && w.isVisibleNow()
+                    && w.mAttrs.type != WindowManager.LayoutParams.TYPE_TOAST) != null) {
                 outContexts.add(dc.getDisplayUiContext());
             }
         }
@@ -2720,16 +2700,16 @@
             if (task.mLayerRank != oldRank) {
                 task.forAllActivities(activity -> {
                     if (activity.hasProcess()) {
-                        mTmpTaskLayerChangedProcs.add(activity.app);
+                        mTaskSupervisor.onProcessActivityStateChanged(activity.app,
+                                true /* forceBatch */);
                     }
                 });
             }
         }, true /* traverseTopToBottom */);
 
-        for (int i = mTmpTaskLayerChangedProcs.size() - 1; i >= 0; i--) {
-            mTmpTaskLayerChangedProcs.valueAt(i).computeProcessActivityState();
+        if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+            mTaskSupervisor.computeProcessActivityStateBatch();
         }
-        mTmpTaskLayerChangedProcs.clear();
     }
 
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3caf86c..ebf5989 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2041,9 +2041,7 @@
             }
         }
 
-        if (isOrganized()) {
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
-        }
+        dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     private static boolean setTaskDescriptionFromActivityAboveRoot(
@@ -2272,8 +2270,8 @@
         }
         // If the task organizer has changed, then it will already be receiving taskAppeared with
         // the latest task-info thus the task-info won't have changed.
-        if (!taskOrgChanged && isOrganized()) {
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
+        if (!taskOrgChanged) {
+            dispatchTaskInfoChangedIfNeeded(false /* force */);
         }
     }
 
@@ -4100,6 +4098,7 @@
         info.parentTaskId = rootTask == getParent() && rootTask.mCreatedByOrganizer
                 ? rootTask.mTaskId
                 : INVALID_TASK_ID;
+        info.isFocused = isFocused();
     }
 
     @Nullable PictureInPictureParams getPictureInPictureParams() {
@@ -4120,8 +4119,7 @@
                     letterboxActivityBounds)) {
             mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
             // Forcing update to reduce visual jank during the transition.
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                        this, /* force= */ true);
+            dispatchTaskInfoChangedIfNeeded(true /* force */);
         }
     }
 
@@ -5067,12 +5065,11 @@
      */
     void onWindowFocusChanged(boolean hasFocus) {
         updateShadowsRadius(hasFocus, getSyncTransaction());
+        dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
-        if (isOrganized()) {
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */);
-        }
+        dispatchTaskInfoChangedIfNeeded(true /* force */);
     }
 
     /**
@@ -7447,9 +7444,7 @@
 
     @Override
     void onChildPositionChanged(WindowContainer child) {
-        if (isOrganized()) {
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
-        }
+        dispatchTaskInfoChangedIfNeeded(false /* force */);
 
         if (!mChildren.contains(child)) {
             return;
@@ -7604,6 +7599,12 @@
         return super.getBounds();
     }
 
+    void dispatchTaskInfoChangedIfNeeded(boolean force) {
+        if (isOrganized()) {
+            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, force);
+        }
+    }
+
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index e64c047..009a7ef 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -27,7 +27,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.TaskDescription;
 import android.app.WindowConfiguration;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -52,7 +51,6 @@
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
 
@@ -538,16 +536,7 @@
         }
         mTmpTaskInfo.configuration.unset();
         task.fillTaskInfo(mTmpTaskInfo);
-        boolean changed = lastInfo == null
-                || mTmpTaskInfo.topActivityType != lastInfo.topActivityType
-                || mTmpTaskInfo.isResizeable != lastInfo.isResizeable
-                || !Objects.equals(
-                        mTmpTaskInfo.positionInParent,
-                        lastInfo.positionInParent)
-                || isLetterboxInfoChanged(lastInfo, mTmpTaskInfo)
-                || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
-                || mTmpTaskInfo.getWindowingMode() != lastInfo.getWindowingMode()
-                || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription);
+        boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo);
         if (!changed) {
             int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
             final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
@@ -582,20 +571,6 @@
         }
     }
 
-    private boolean isLetterboxInfoChanged(
-                final RunningTaskInfo lastInfo, final RunningTaskInfo currentInfo) {
-        return !Objects.equals(
-                        currentInfo.letterboxActivityBounds,
-                        lastInfo.letterboxActivityBounds)
-                || !Objects.equals(
-                        currentInfo.getConfiguration().windowConfiguration.getBounds(),
-                        lastInfo.getConfiguration().windowConfiguration.getBounds())
-                || !Objects.equals(
-                        currentInfo.getConfiguration().windowConfiguration.getMaxBounds(),
-                        lastInfo.getConfiguration().windowConfiguration.getMaxBounds())
-                || !Objects.equals(currentInfo.parentBounds, lastInfo.parentBounds);
-    }
-
     @Override
     public WindowContainerToken getImeTarget(int displayId) {
         enforceTaskPermission("getImeTarget()");
diff --git a/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
index 7f62630..0e2b71c 100644
--- a/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
+++ b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
@@ -21,7 +21,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 
+import java.io.PrintWriter;
 import java.util.concurrent.Executor;
+import java.util.function.Predicate;
 
 /**
  * A quick lookup for all processes with visible activities. It also tracks the CPU usage of
@@ -70,10 +72,22 @@
     }
 
     boolean hasResumedActivity(int uid) {
+        return match(uid, WindowProcessController::hasResumedActivity);
+    }
+
+    /**
+     * Returns {@code true} if the uid has a process that contains an activity with
+     * {@link ActivityRecord#mVisibleRequested} or {@link ActivityRecord#isVisible()} is true.
+     */
+    boolean hasVisibleActivity(int uid) {
+        return match(uid, null /* predicate */);
+    }
+
+    private boolean match(int uid, Predicate<WindowProcessController> predicate) {
         synchronized (mProcMap) {
             for (int i = mProcMap.size() - 1; i >= 0; i--) {
                 final WindowProcessController wpc = mProcMap.keyAt(i);
-                if (wpc.mUid == uid && wpc.hasResumedActivity()) {
+                if (wpc.mUid == uid && (predicate == null || predicate.test(wpc))) {
                     return true;
                 }
             }
@@ -87,6 +101,16 @@
         }
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix + "VisibleActivityProcess:[");
+        synchronized (mProcMap) {
+            for (int i = mProcMap.size() - 1; i >= 0; i--) {
+                pw.print(" " + mProcMap.keyAt(i));
+            }
+        }
+        pw.println("]");
+    }
+
     /**
      * Get CPU time in background thread because it will access proc files or the lock of cpu
      * tracker is held by a background thread.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index ff5c174..f627ca6 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -145,6 +145,9 @@
         ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
         mService.openSurfaceTransaction();
         try {
+            // Remove all deferred displays, tasks, and activities.
+            mService.mRoot.handleCompleteDeferredRemoval();
+
             final AccessibilityController accessibilityController =
                     mService.mAccessibilityController;
             final int numDisplays = mDisplayContentsAnimators.size();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b25fbc0..4574be7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1112,7 +1112,7 @@
                 // descendant. E.g. if a display is pending to be removed because it contains an
                 // activity with {@link ActivityRecord#mIsExiting} is true, the display may be
                 // removed when completing the removal of the last activity from
-                // {@link ActivityRecord#checkCompleteDeferredRemoval}.
+                // {@link ActivityRecord#handleCompleteDeferredRemoval}.
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7c7dd6f..580c088 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2978,7 +2978,7 @@
                         displayContent, true /* includingParents */);
             }
         }
-        syncInputTransactions();
+        syncInputTransactions(true /* waitForAnimations */);
     }
 
     /**
@@ -7979,7 +7979,8 @@
     }
 
     @Override
-    public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
+    public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode,
+            boolean waitForAnimations) {
         boolean isDown;
         boolean isUp;
 
@@ -7998,21 +7999,23 @@
         // For all mouse events, also sync before injecting.
         // For ACTION_UP, sync after injecting.
         if (isDown || isMouseEvent) {
-            syncInputTransactions();
+            syncInputTransactions(waitForAnimations);
         }
         final boolean result =
                 LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode);
         if (isUp) {
-            syncInputTransactions();
+            syncInputTransactions(waitForAnimations);
         }
         return result;
     }
 
     @Override
-    public void syncInputTransactions() {
+    public void syncInputTransactions(boolean waitForAnimations) {
         final long token = Binder.clearCallingIdentity();
         try {
-            waitForAnimationsToComplete();
+            if (waitForAnimations) {
+                waitForAnimationsToComplete();
+            }
 
             // Collect all input transactions from all displays to make sure we could sync all input
             // windows at same time.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 88fd361..c55f059 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -607,7 +607,7 @@
 
     private boolean isBoundByForegroundUid() {
         for (int i = mBoundClientUids.size() - 1; i >= 0; --i) {
-            if (mAtm.isUidForeground(mBoundClientUids.valueAt(i))) {
+            if (mAtm.hasActiveVisibleWindow(mBoundClientUids.valueAt(i))) {
                 return true;
             }
         }
@@ -1049,16 +1049,6 @@
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = mActivities.get(i);
-            if (r.app != this) {
-                Slog.e(TAG, "Found activity " + r + " in proc activity list using " + r.app
-                        + " instead of expected " + this);
-                if (r.app == null || (r.app.mUid == mUid)) {
-                    // Only fix things up when they look valid.
-                    r.setProcess(this);
-                } else {
-                    continue;
-                }
-            }
             if (r.isVisible()) {
                 stateFlags |= ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
             }
@@ -1115,6 +1105,7 @@
     /** Called when the process has some oom related changes and it is going to update oom-adj. */
     private void prepareOomAdjustment() {
         mAtm.mRootWindowContainer.rankTaskLayersIfNeeded();
+        mAtm.mTaskSupervisor.computeProcessActivityStateBatch();
     }
 
     public int computeRelaunchReason() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b5509f6..0a8ff4d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3286,6 +3286,11 @@
             logExclusionRestrictions(EXCLUSION_LEFT);
             logExclusionRestrictions(EXCLUSION_RIGHT);
         }
+        // Exclude toast because legacy apps may show toast window by themselves, so the misused
+        // apps won't always be considered as foreground state.
+        if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) {
+            mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
+        }
     }
 
     private void logExclusionRestrictions(int side) {
@@ -5624,23 +5629,6 @@
                 true /* ignoreVisibility */));
     }
 
-    /**
-     * Returns {@code true} if this window is not {@link WindowManager.LayoutParams#TYPE_TOAST}
-     * or {@link WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
-     * since this window doesn't belong to apps.
-     */
-    boolean isNonToastOrStarting() {
-        return mAttrs.type != TYPE_TOAST && mAttrs.type != TYPE_APPLICATION_STARTING;
-    }
-
-    boolean isNonToastWindowVisibleForUid(int callingUid) {
-        return getOwningUid() == callingUid && isNonToastOrStarting() && isVisibleNow();
-    }
-
-    boolean isNonToastWindowVisibleForPid(int pid) {
-        return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
-    }
-
     void setViewVisibility(int viewVisibility) {
         mViewVisibility = viewVisibility;
     }
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index fb55e75..d1918d8 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -28,3 +28,10 @@
     api_dir: "cec-config/schema",
     package_name: "com.android.server.hdmi.cec.config",
 }
+
+xsd_config {
+    name: "device-state-config",
+    srcs: ["device-state-config/device-state-config.xsd"],
+    api_dir: "device-state-config/schema",
+    package_name: "com.android.server.policy.devicestate.config",
+}
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
new file mode 100644
index 0000000..0d8c08c
--- /dev/null
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:element name="device-state-config">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="device-state" type="deviceState" maxOccurs="256" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:complexType name="deviceState">
+        <xs:sequence>
+            <xs:element name="identifier">
+                <xs:simpleType>
+                    <xs:restriction base="xs:integer">
+                        <xs:minInclusive value="0" />
+                        <xs:maxInclusive value="255" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:element>
+            <xs:element name="name" type="xs:string" minOccurs="0" />
+            <xs:element name="conditions" type="conditions" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="conditions">
+        <xs:sequence>
+            <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0" />
+            <xs:element name="sensor" type="sensorCondition" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="lidSwitchCondition">
+        <xs:sequence>
+            <xs:element name="open" type="xs:boolean" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="sensorCondition">
+        <xs:sequence>
+            <xs:element name="name" type="xs:string" />
+            <xs:element name="type" type="xs:positiveInteger" />
+            <xs:element name="value" type="numericRange" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="numericRange">
+        <xs:sequence>
+            <xs:choice minOccurs="0">
+                <xs:element name="min" type="xs:decimal" />
+                <xs:element name="min-inclusive" type="xs:decimal" />
+            </xs:choice>
+            <xs:choice minOccurs="0">
+                <xs:element name="max" type="xs:decimal" />
+                <xs:element name="max-inclusive" type="xs:decimal"/>
+            </xs:choice>
+        </xs:sequence>
+    </xs:complexType>
+</xs:schema>
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
new file mode 100644
index 0000000..667d1ad
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -0,0 +1,61 @@
+// Signature format: 2.0
+package com.android.server.policy.devicestate.config {
+
+  public class Conditions {
+    ctor public Conditions();
+    method public com.android.server.policy.devicestate.config.LidSwitchCondition getLidSwitch();
+    method public java.util.List<com.android.server.policy.devicestate.config.SensorCondition> getSensor();
+    method public void setLidSwitch(com.android.server.policy.devicestate.config.LidSwitchCondition);
+  }
+
+  public class DeviceState {
+    ctor public DeviceState();
+    method public com.android.server.policy.devicestate.config.Conditions getConditions();
+    method public java.math.BigInteger getIdentifier();
+    method public String getName();
+    method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
+    method public void setIdentifier(java.math.BigInteger);
+    method public void setName(String);
+  }
+
+  public class DeviceStateConfig {
+    ctor public DeviceStateConfig();
+    method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
+  }
+
+  public class LidSwitchCondition {
+    ctor public LidSwitchCondition();
+    method public boolean getOpen();
+    method public void setOpen(boolean);
+  }
+
+  public class NumericRange {
+    ctor public NumericRange();
+    method public java.math.BigDecimal getMaxInclusive_optional();
+    method public java.math.BigDecimal getMax_optional();
+    method public java.math.BigDecimal getMinInclusive_optional();
+    method public java.math.BigDecimal getMin_optional();
+    method public void setMaxInclusive_optional(java.math.BigDecimal);
+    method public void setMax_optional(java.math.BigDecimal);
+    method public void setMinInclusive_optional(java.math.BigDecimal);
+    method public void setMin_optional(java.math.BigDecimal);
+  }
+
+  public class SensorCondition {
+    ctor public SensorCondition();
+    method public String getName();
+    method public java.math.BigInteger getType();
+    method public java.util.List<com.android.server.policy.devicestate.config.NumericRange> getValue();
+    method public void setName(String);
+    method public void setType(java.math.BigInteger);
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.policy.devicestate.config.DeviceStateConfig read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/services/core/xsd/device-state-config/schema/last_current.txt b/services/core/xsd/device-state-config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/last_current.txt
diff --git a/services/core/xsd/device-state-config/schema/last_removed.txt b/services/core/xsd/device-state-config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/last_removed.txt
diff --git a/services/core/xsd/device-state-config/schema/removed.txt b/services/core/xsd/device-state-config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/device-state-config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f08ce27..a646682 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4373,7 +4373,7 @@
             final int adminUser = admin.getUserHandle().getIdentifier();
             // Password complexity is only taken into account from DO/PO
             if (isDeviceOwner(adminComponent, adminUser)
-                    || isProfileOwner(adminComponent, adminUser)) {
+                    || isProfileOwnerUncheckedLocked(adminComponent, adminUser)) {
                 maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
             }
         }
@@ -6216,7 +6216,7 @@
         }
         Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
-        final CallerIdentity caller = getAdminCallerIdentity(comp);
+        final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
 
@@ -7487,6 +7487,12 @@
         return who != null && who.equals(profileOwner);
     }
 
+    private boolean isProfileOwnerUncheckedLocked(ComponentName who, int userId) {
+        ensureLocked();
+        final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+        return who != null && who.equals(profileOwner);
+    }
+
     /**
      * Returns {@code true} if the provided caller identity is of a profile owner.
      * @param caller identity of caller.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 77fef12..8795d77 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -284,6 +284,8 @@
                 return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
             case RARE_INDEX:
                 return UsageStatsManager.STANDBY_BUCKET_RARE;
+            case RESTRICTED_INDEX:
+                return UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
             default:
                 return UsageStatsManager.STANDBY_BUCKET_NEVER;
         }
@@ -292,6 +294,7 @@
     private void setStandbyBucket(int bucketIndex) {
         when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
                 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
+        mQuotaController.updateStandbyBucket(SOURCE_USER_ID, SOURCE_PACKAGE, bucketIndex);
     }
 
     private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
index be6bc99..ac23d4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
@@ -100,6 +100,11 @@
     }
 
     @Override
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    @Override
     protected int[] getProfileIds(int userId) {
         IntArray profiles = mProfiles.get(userId);
         if (profiles != null) {
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index e76c5a4..a02c533 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
@@ -36,6 +37,7 @@
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.test.mock.MockContentResolver;
+import android.testing.TestableLooper;
 import android.util.MutableBoolean;
 import android.view.KeyEvent;
 
@@ -44,6 +46,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -65,6 +68,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class GestureLauncherServiceTest {
 
     private static final int FAKE_USER_ID = 1337;
@@ -83,6 +87,7 @@
     private @Mock StatusBarManagerInternal mStatusBarManagerInternal;
     private @Mock TelecomManager mTelecomManager;
     private @Mock MetricsLogger mMetricsLogger;
+    @Mock private UiEventLogger mUiEventLogger;
     private MockContentResolver mContentResolver;
     private GestureLauncherService mGestureLauncherService;
 
@@ -109,7 +114,8 @@
         when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
         when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
 
-        mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger);
+        mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
+                mUiEventLogger);
     }
 
     @Test
@@ -268,6 +274,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -312,6 +319,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -358,6 +366,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -406,6 +415,8 @@
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
         verify(mMetricsLogger)
             .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -460,6 +471,8 @@
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
         verify(mMetricsLogger)
             .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
 
         final ArgumentCaptor<Integer> cameraIntervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -499,7 +512,8 @@
         assertTrue(intercepted);
         assertTrue(outLaunched.value);
 
-        // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
         verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -551,7 +565,8 @@
         assertTrue(outLaunched.value);
         assertTrue(intercepted);
 
-        // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
         verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -646,6 +661,7 @@
 
         verify(mMetricsLogger, never())
                 .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(1)).histogram(
@@ -690,6 +706,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -736,6 +753,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -782,6 +800,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -826,6 +845,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -869,6 +889,7 @@
         assertFalse(outLaunched.value);
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -914,6 +935,7 @@
         assertFalse(outLaunched.value);
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -961,6 +983,8 @@
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
         verify(mMetricsLogger)
             .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -1007,6 +1031,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -1051,6 +1076,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
@@ -1097,6 +1123,7 @@
 
         verify(mMetricsLogger, never())
             .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+        verify(mUiEventLogger, never()).log(any());
 
         final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mMetricsLogger, times(2)).histogram(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 1cdd873..e43a002 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -66,7 +66,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.IntConsumer;
@@ -129,6 +128,8 @@
     ScaleChangedListener mMockScaleChangedListener;
     @Mock
     MagnificationRequestObserver mMagnificationRequestObserver;
+    @Mock
+    WindowMagnificationPromptController mWindowMagnificationPromptController;
 
     private OffsettableClock mClock;
     private FullScreenMagnificationGestureHandler mMgh;
@@ -170,7 +171,9 @@
 
     @After
     public void tearDown() {
+        mMgh.onDestroy();
         mFullScreenMagnificationController.unregister(DISPLAY_0);
+        verify(mWindowMagnificationPromptController).onDestroy();
     }
 
     @NonNull
@@ -178,7 +181,8 @@
             boolean detectShortcutTrigger) {
         FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
                 mContext, mFullScreenMagnificationController, mMockScaleChangedListener,
-                detectTripleTap, detectShortcutTrigger, DISPLAY_0);
+                detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController,
+                DISPLAY_0);
         mHandler = new TestHandler(h.mDetectingState, mClock) {
             @Override
             protected String messageToString(Message m) {
@@ -434,6 +438,20 @@
         returnToNormalFrom(STATE_PANNING);
     }
 
+    @Test
+    public void testZoomedWithTripleTap_invokeShowWindowPromptAction() {
+        goFromStateIdleTo(STATE_ZOOMED);
+
+        verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
+    }
+
+    @Test
+    public void testShortcutTriggered_invokeShowWindowPromptAction() {
+        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+
+        verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
+    }
+
     private void assertActionsInOrder(List<MotionEvent> actualEvents,
             List<Integer> expectedActions) {
         assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
new file mode 100644
index 0000000..5fd28f57
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
+
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
+import static com.android.server.accessibility.magnification.WindowMagnificationPromptController.ACTION_TURN_ON_IN_SETTINGS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowMagnificationPromptController}.
+ */
+public class WindowMagnificationPromptControllerTest {
+
+    private static final int TEST_USER = 0;
+
+    @Mock
+    private NotificationManager mNotificationManager;
+    @Mock
+    private StatusBarManager mStatusBarManager;
+    @Rule
+    public A11yTestableContext mTestableContext = new A11yTestableContext(
+            InstrumentationRegistry.getContext());
+    private ContentResolver mResolver = mTestableContext.getContentResolver();
+    private WindowMagnificationPromptController mWindowMagnificationPromptController;
+    private BroadcastReceiver mReceiver;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+        mTestableContext.addMockSystemService(StatusBarManager.class, mStatusBarManager);
+        setWindowMagnificationPromptSettings(true);
+        mWindowMagnificationPromptController = new WindowMagnificationPromptController(
+                mTestableContext, TEST_USER);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mWindowMagnificationPromptController.onDestroy();
+    }
+
+    @Test
+    public void showNotificationIfNeeded_promptSettingsIsOn_showNotification() {
+        mWindowMagnificationPromptController.showNotificationIfNeeded();
+
+        verify(mNotificationManager).notify(eq(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE), any(
+                Notification.class));
+    }
+
+    @Test
+    public void tapTurnOnAction_isShown_cancelNotificationAndLaunchMagnificationSettings() {
+        showNotificationAndAssert();
+
+        final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
+        mReceiver.onReceive(mTestableContext, intent);
+
+        verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+        verifyLaunchMagnificationSettings();
+    }
+
+    @Test
+    public void tapTurnOnAction_isShown_settingsValueIsFalseAndUnregisterReceiver() {
+        showNotificationAndAssert();
+
+        final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
+        mReceiver.onReceive(mTestableContext, intent);
+
+        assertThat(Settings.Secure.getInt(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+                -1)).isEqualTo(0);
+        verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+    }
+
+    @Test
+    public void tapDismissAction_isShown_cancelNotificationAndUnregisterReceiver() {
+        showNotificationAndAssert();
+
+        final Intent intent = new Intent(WindowMagnificationPromptController.ACTION_DISMISS);
+        mReceiver.onReceive(mTestableContext, intent);
+
+        verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+        verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+    }
+
+    @Test
+    public void promptSettingsChangeToFalse_isShown_cancelNotificationAndUnregisterReceiver() {
+        showNotificationAndAssert();
+
+        setWindowMagnificationPromptSettings(false);
+
+        verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+        verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+    }
+
+    @Test
+    public void onDestroy_isShown_cancelNotificationAndUnregisterReceiver() {
+        showNotificationAndAssert();
+
+        mWindowMagnificationPromptController.onDestroy();
+
+        verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
+        verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
+    }
+
+    private void verifyLaunchMagnificationSettings() {
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(
+                UserHandle.class);
+        verify(mTestableContext.getSpyContext()).startActivityAsUser(intentCaptor.capture(),
+                bundleCaptor.capture(), userHandleCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+        assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER);
+        verify(mStatusBarManager).collapsePanels();
+    }
+
+    private void showNotificationAndAssert() {
+        mWindowMagnificationPromptController.showNotificationIfNeeded();
+        mReceiver = mWindowMagnificationPromptController.mNotificationActionReceiver;
+        assertThat(mReceiver).isNotNull();
+    }
+
+    private void setWindowMagnificationPromptSettings(boolean enable) {
+        Settings.Secure.putIntForUser(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+                enable ? 1 : 0, TEST_USER);
+        if (mWindowMagnificationPromptController != null) {
+            mWindowMagnificationPromptController.onPromptSettingsValueChanged();
+        }
+    }
+
+    private class A11yTestableContext extends TestableContext {
+
+        private Context mSpyContext;
+
+        A11yTestableContext(Context base) {
+            super(base);
+            mSpyContext = Mockito.mock(Context.class);
+        }
+
+        @Override
+        public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+            mSpyContext.startActivityAsUser(intent, options, user);
+        }
+
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler) {
+            return mSpyContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+        }
+
+        @Override
+        public void unregisterReceiver(BroadcastReceiver receiver) {
+            mSpyContext.unregisterReceiver(receiver);
+        }
+
+        Context getSpyContext() {
+            return mSpyContext;
+        }
+    }
+}
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 726e48a..b929061 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -26,15 +26,17 @@
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.exceptions.AppSearchException;
 
+import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
 import com.android.server.appsearch.proto.DocumentProto;
 import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
-import com.android.server.appsearch.proto.IndexingConfig;
 import com.android.server.appsearch.proto.PropertyConfigProto;
 import com.android.server.appsearch.proto.PropertyProto;
 import com.android.server.appsearch.proto.SchemaProto;
 import com.android.server.appsearch.proto.SchemaTypeConfigProto;
 import com.android.server.appsearch.proto.SearchSpecProto;
+import com.android.server.appsearch.proto.StringIndexingConfig;
 import com.android.server.appsearch.proto.TermMatchType;
+
 import com.google.common.collect.ImmutableSet;
 
 import org.junit.Before;
@@ -42,18 +44,36 @@
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 public class AppSearchImplTest {
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private AppSearchImpl mAppSearchImpl;
+    private SchemaTypeConfigProto mVisibilitySchemaProto;
 
     @Before
     public void setUp() throws Exception {
         mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+
+        AppSearchSchema visibilityAppSearchSchema =
+                new AppSearchSchema.Builder(
+                                VisibilityStore.DATABASE_NAME
+                                        + AppSearchImpl.DATABASE_DELIMITER
+                                        + VisibilityStore.SCHEMA_TYPE)
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder(
+                                                VisibilityStore.PLATFORM_HIDDEN_PROPERTY)
+                                        .setDataType(
+                                                AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+                                        .build())
+                        .build();
+        mVisibilitySchemaProto = SchemaToProtoConverter.convert(visibilityAppSearchSchema);
     }
 
     /**
@@ -62,91 +82,217 @@
      * schema.
      */
     @Test
-    public void testRewriteSchema() throws Exception {
-        SchemaProto.Builder existingSchemaBuilder = mAppSearchImpl.getSchemaProto().toBuilder();
+    public void testRewriteSchema_addType() throws Exception {
+        SchemaProto.Builder existingSchemaBuilder =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("existingDatabase/Foo")
+                                        .build());
 
-        SchemaProto newSchema = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("Foo").build())
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("TestType")
-                        .addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("subject")
-                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setIndexingConfig(
-                                        IndexingConfig.newBuilder()
-                                                .setTokenizerType(
-                                                        IndexingConfig.TokenizerType.Code.PLAIN)
-                                                .setTermMatchType(TermMatchType.Code.PREFIX)
-                                                .build()
-                                ).build()
-                        ).addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("link")
-                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setSchemaType("RefType")
-                                .build()
-                        ).build()
-                ).build();
+        // Create a copy so we can modify it.
+        List<SchemaTypeConfigProto> existingTypes =
+                new ArrayList<>(existingSchemaBuilder.getTypesList());
 
-        Set<String> newTypes = mAppSearchImpl.rewriteSchema("databaseName", existingSchemaBuilder,
-                newSchema);
-        assertThat(newTypes).containsExactly("databaseName/Foo", "databaseName/TestType");
+        SchemaProto newSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("TestType")
+                                        .addProperties(
+                                                PropertyConfigProto.newBuilder()
+                                                        .setPropertyName("subject")
+                                                        .setDataType(
+                                                                PropertyConfigProto.DataType.Code
+                                                                        .STRING)
+                                                        .setCardinality(
+                                                                PropertyConfigProto.Cardinality.Code
+                                                                        .OPTIONAL)
+                                                        .setStringIndexingConfig(
+                                                                StringIndexingConfig.newBuilder()
+                                                                        .setTokenizerType(
+                                                                                StringIndexingConfig
+                                                                                        .TokenizerType
+                                                                                        .Code.PLAIN)
+                                                                        .setTermMatchType(
+                                                                                TermMatchType.Code
+                                                                                        .PREFIX)
+                                                                        .build())
+                                                        .build())
+                                        .addProperties(
+                                                PropertyConfigProto.newBuilder()
+                                                        .setPropertyName("link")
+                                                        .setDataType(
+                                                                PropertyConfigProto.DataType.Code
+                                                                        .DOCUMENT)
+                                                        .setCardinality(
+                                                                PropertyConfigProto.Cardinality.Code
+                                                                        .OPTIONAL)
+                                                        .setSchemaType("RefType")
+                                                        .build())
+                                        .build())
+                        .build();
 
-        SchemaProto expectedSchema = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("databaseName/Foo").build())
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("databaseName/TestType")
-                        .addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("subject")
-                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setIndexingConfig(
-                                        IndexingConfig.newBuilder()
-                                                .setTokenizerType(
-                                                        IndexingConfig.TokenizerType.Code.PLAIN)
-                                                .setTermMatchType(TermMatchType.Code.PREFIX)
-                                                .build()
-                                ).build()
-                        ).addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("link")
-                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setSchemaType("databaseName/RefType")
-                                .build()
-                        ).build())
-                .build();
+        AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+                mAppSearchImpl.rewriteSchema("newDatabase", existingSchemaBuilder, newSchema);
+
+        // We rewrote all the new types that were added. And nothing was removed.
+        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+                .containsExactly("newDatabase/Foo", "newDatabase/TestType");
+        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+
+        SchemaProto expectedSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("newDatabase/Foo")
+                                        .build())
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("newDatabase/TestType")
+                                        .addProperties(
+                                                PropertyConfigProto.newBuilder()
+                                                        .setPropertyName("subject")
+                                                        .setDataType(
+                                                                PropertyConfigProto.DataType.Code
+                                                                        .STRING)
+                                                        .setCardinality(
+                                                                PropertyConfigProto.Cardinality.Code
+                                                                        .OPTIONAL)
+                                                        .setStringIndexingConfig(
+                                                                StringIndexingConfig.newBuilder()
+                                                                        .setTokenizerType(
+                                                                                StringIndexingConfig
+                                                                                        .TokenizerType
+                                                                                        .Code.PLAIN)
+                                                                        .setTermMatchType(
+                                                                                TermMatchType.Code
+                                                                                        .PREFIX)
+                                                                        .build())
+                                                        .build())
+                                        .addProperties(
+                                                PropertyConfigProto.newBuilder()
+                                                        .setPropertyName("link")
+                                                        .setDataType(
+                                                                PropertyConfigProto.DataType.Code
+                                                                        .DOCUMENT)
+                                                        .setCardinality(
+                                                                PropertyConfigProto.Cardinality.Code
+                                                                        .OPTIONAL)
+                                                        .setSchemaType("newDatabase/RefType")
+                                                        .build())
+                                        .build())
+                        .build();
+
+        existingTypes.addAll(expectedSchema.getTypesList());
+        assertThat(existingSchemaBuilder.getTypesList()).containsExactlyElementsIn(existingTypes);
+    }
+
+    /**
+     * Ensure that we track all types that were rewritten in the input schema. Even if they were not
+     * technically "added" to the existing schema.
+     */
+    @Test
+    public void testRewriteSchema_rewriteType() throws Exception {
+        SchemaProto.Builder existingSchemaBuilder =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("existingDatabase/Foo")
+                                        .build());
+
+        SchemaProto newSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
+                        .build();
+
+        AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+                mAppSearchImpl.rewriteSchema("existingDatabase", existingSchemaBuilder, newSchema);
+
+        // Nothing was removed, but the method did rewrite the type name.
+        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+                .containsExactly("existingDatabase/Foo");
+        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+
+        // Same schema since nothing was added.
+        SchemaProto expectedSchema = existingSchemaBuilder.build();
+        assertThat(existingSchemaBuilder.getTypesList())
+                .containsExactlyElementsIn(expectedSchema.getTypesList());
+    }
+
+    /**
+     * Ensure that we track which types from the existing schema are deleted when a new schema is
+     * set.
+     */
+    @Test
+    public void testRewriteSchema_deleteType() throws Exception {
+        SchemaProto.Builder existingSchemaBuilder =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("existingDatabase/Foo")
+                                        .build());
+
+        SchemaProto newSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Bar").build())
+                        .build();
+
+        AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
+                mAppSearchImpl.rewriteSchema("existingDatabase", existingSchemaBuilder, newSchema);
+
+        // Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
+        // new schema.
+        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
+                .containsExactly("existingDatabase/Bar");
+        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes)
+                .containsExactly("existingDatabase/Foo");
+
+        // Same schema since nothing was added.
+        SchemaProto expectedSchema =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("existingDatabase/Bar")
+                                        .build())
+                        .build();
+
         assertThat(existingSchemaBuilder.getTypesList())
                 .containsExactlyElementsIn(expectedSchema.getTypesList());
     }
 
     @Test
     public void testAddDocumentTypePrefix() {
-        DocumentProto insideDocument = DocumentProto.newBuilder()
-                .setUri("inside-uri")
-                .setSchema("type")
-                .setNamespace("namespace")
-                .build();
-        DocumentProto documentProto = DocumentProto.newBuilder()
-                .setUri("uri")
-                .setSchema("type")
-                .setNamespace("namespace")
-                .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
-                .build();
+        DocumentProto insideDocument =
+                DocumentProto.newBuilder()
+                        .setUri("inside-uri")
+                        .setSchema("type")
+                        .setNamespace("namespace")
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri")
+                        .setSchema("type")
+                        .setNamespace("namespace")
+                        .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+                        .build();
 
-        DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
-                .setUri("inside-uri")
-                .setSchema("databaseName/type")
-                .setNamespace("databaseName/namespace")
-                .build();
-        DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
-                .setUri("uri")
-                .setSchema("databaseName/type")
-                .setNamespace("databaseName/namespace")
-                .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
-                .build();
+        DocumentProto expectedInsideDocument =
+                DocumentProto.newBuilder()
+                        .setUri("inside-uri")
+                        .setSchema("databaseName/type")
+                        .setNamespace("databaseName/namespace")
+                        .build();
+        DocumentProto expectedDocumentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri")
+                        .setSchema("databaseName/type")
+                        .setNamespace("databaseName/namespace")
+                        .addProperties(
+                                PropertyProto.newBuilder()
+                                        .addDocumentValues(expectedInsideDocument))
+                        .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
         mAppSearchImpl.addPrefixToDocument(actualDocument, "databaseName/");
@@ -154,31 +300,37 @@
     }
 
     @Test
-    public void testRemoveDocumentTypePrefixes() {
-        DocumentProto insideDocument = DocumentProto.newBuilder()
-                .setUri("inside-uri")
-                .setSchema("databaseName1/type")
-                .setNamespace("databaseName2/namespace")
-                .build();
-        DocumentProto documentProto = DocumentProto.newBuilder()
-                .setUri("uri")
-                .setSchema("databaseName2/type")
-                .setNamespace("databaseName3/namespace")
-                .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
-                .build();
+    public void testRemoveDocumentTypePrefixes() throws Exception {
+        DocumentProto insideDocument =
+                DocumentProto.newBuilder()
+                        .setUri("inside-uri")
+                        .setSchema("databaseName1/type")
+                        .setNamespace("databaseName2/namespace")
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri")
+                        .setSchema("databaseName2/type")
+                        .setNamespace("databaseName3/namespace")
+                        .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+                        .build();
 
-        DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
-                .setUri("inside-uri")
-                .setSchema("type")
-                .setNamespace("namespace")
-                .build();
+        DocumentProto expectedInsideDocument =
+                DocumentProto.newBuilder()
+                        .setUri("inside-uri")
+                        .setSchema("type")
+                        .setNamespace("namespace")
+                        .build();
         // Since we don't pass in "databaseName3/" as a prefix to remove, it stays on the Document.
-        DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
-                .setUri("uri")
-                .setSchema("type")
-                .setNamespace("namespace")
-                .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
-                .build();
+        DocumentProto expectedDocumentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri")
+                        .setSchema("type")
+                        .setNamespace("namespace")
+                        .addProperties(
+                                PropertyProto.newBuilder()
+                                        .addDocumentValues(expectedInsideDocument))
+                        .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
         mAppSearchImpl.removeDatabasesFromDocument(actualDocument);
@@ -190,19 +342,23 @@
         // Insert schema
         Set<AppSearchSchema> schemas =
                 Collections.singleton(new AppSearchSchema.Builder("type").build());
-        mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
 
         // Insert enough documents.
-        for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
-                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+        for (int i = 0;
+                i
+                        < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+                                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
+                i++) {
             GenericDocument document =
-                    new GenericDocument.Builder("uri" + i, "type").setNamespace(
-                            "namespace").build();
+                    new GenericDocument.Builder("uri" + i, "type")
+                            .setNamespace("namespace")
+                            .build();
             mAppSearchImpl.putDocument("database", document);
         }
 
         // Check optimize() will release 0 docs since there is no deletion.
-        GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
 
         // delete 999 documents , we will reach the threshold to trigger optimize() in next
@@ -212,82 +368,82 @@
         }
 
         // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
-        optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs())
                 .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
 
         // Keep delete docs, will reach the interval this time and trigger optimize().
         for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
-                i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
-                        + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+                i
+                        < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+                                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
+                i++) {
             mAppSearchImpl.remove("database", "namespace", "uri" + i);
         }
 
         // Verify optimize() is triggered
-        optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs())
                 .isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
     }
 
     @Test
-    public void testRewriteSearchSpec_OneInstance() throws Exception {
-        SearchSpecProto.Builder searchSpecProto =
-                SearchSpecProto.newBuilder().setQuery("");
+    public void testRewriteSearchSpec_oneInstance() throws Exception {
+        SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
 
         // Insert schema
         Set<AppSearchSchema> schemas =
                 Collections.singleton(new AppSearchSchema.Builder("type").build());
-        mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
 
         // Insert document
-        GenericDocument document = new GenericDocument.Builder("uri", "type").setNamespace(
-                "namespace").build();
+        GenericDocument document =
+                new GenericDocument.Builder("uri", "type").setNamespace("namespace").build();
         mAppSearchImpl.putDocument("database", document);
 
         // Rewrite SearchSpec
-        mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto, Collections.singleton(
-                "database"));
+        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
+                searchSpecProto, Collections.singleton("database"));
         assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
         assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
     }
 
     @Test
-    public void testRewriteSearchSpec_TwoInstances() throws Exception {
-        SearchSpecProto.Builder searchSpecProto =
-                SearchSpecProto.newBuilder().setQuery("");
+    public void testRewriteSearchSpec_twoInstances() throws Exception {
+        SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
 
         // Insert schema
-        Set<AppSearchSchema> schemas = Set.of(
-                new AppSearchSchema.Builder("typeA").build(),
-                new AppSearchSchema.Builder("typeB").build());
-        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
-        mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
+        Set<AppSearchSchema> schemas =
+                Set.of(
+                        new AppSearchSchema.Builder("typeA").build(),
+                        new AppSearchSchema.Builder("typeB").build());
+        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
+        mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
 
         // Insert documents
-        GenericDocument document1 = new GenericDocument.Builder("uri", "typeA").setNamespace(
-                "namespace").build();
+        GenericDocument document1 =
+                new GenericDocument.Builder("uri", "typeA").setNamespace("namespace").build();
         mAppSearchImpl.putDocument("database1", document1);
 
-        GenericDocument document2 = new GenericDocument.Builder("uri", "typeB").setNamespace(
-                "namespace").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder("uri", "typeB").setNamespace("namespace").build();
         mAppSearchImpl.putDocument("database2", document2);
 
         // Rewrite SearchSpec
-        mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto,
-                ImmutableSet.of("database1", "database2"));
-        assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
-                "database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
-        assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly(
-                "database1/namespace", "database2/namespace");
+        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
+                searchSpecProto, ImmutableSet.of("database1", "database2"));
+        assertThat(searchSpecProto.getSchemaTypeFiltersList())
+                .containsExactly(
+                        "database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
+        assertThat(searchSpecProto.getNamespaceFiltersList())
+                .containsExactly("database1/namespace", "database2/namespace");
     }
 
     @Test
     public void testQueryEmptyDatabase() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
-        SearchResultPage searchResultPage = mAppSearchImpl.query(
-                "EmptyDatabase",
-                "", searchSpec);
+        SearchResultPage searchResultPage = mAppSearchImpl.query("EmptyDatabase", "", searchSpec);
         assertThat(searchResultPage.getResults()).isEmpty();
     }
 
@@ -295,25 +451,25 @@
     public void testGlobalQueryEmptyDatabase() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
-        SearchResultPage searchResultPage = mAppSearchImpl.query(
-                "EmptyDatabase",
-                "", searchSpec);
+        SearchResultPage searchResultPage = mAppSearchImpl.query("EmptyDatabase", "", searchSpec);
         assertThat(searchResultPage.getResults()).isEmpty();
     }
 
     @Test
-    public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
+    public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
         SearchSpec searchSpec =
-                new SearchSpec.Builder().addSchema("FakeType").setTermMatch(
-                        TermMatchType.Code.PREFIX_VALUE).build();
-        mAppSearchImpl.removeByQuery("EmptyDatabase",
-                "", searchSpec);
+                new SearchSpec.Builder()
+                        .addSchemaType("FakeType")
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .build();
+        mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
 
         searchSpec =
-                new SearchSpec.Builder().addNamespace("FakeNamespace").setTermMatch(
-                        TermMatchType.Code.PREFIX_VALUE).build();
-        mAppSearchImpl.removeByQuery("EmptyDatabase",
-                "", searchSpec);
+                new SearchSpec.Builder()
+                        .addNamespace("FakeNamespace")
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .build();
+        mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
 
         searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
         mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
@@ -324,14 +480,46 @@
         Set<AppSearchSchema> schemas =
                 Collections.singleton(new AppSearchSchema.Builder("Email").build());
         // Set schema Email to AppSearch database1
-        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
 
-        // Create excepted schemaType proto.
-        SchemaProto exceptedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .build();
-        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
-                .containsExactlyElementsIn(exceptedProto.getTypesList());
+        // Create expected schemaType proto.
+        SchemaProto expectedProto =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                        .build();
+
+        List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+        expectedTypes.add(mVisibilitySchemaProto);
+        expectedTypes.addAll(expectedProto.getTypesList());
+        assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+                .containsExactlyElementsIn(expectedTypes);
+    }
+
+    @Test
+    public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+                /*forceOverride=*/ false);
+        mAppSearchImpl.setVisibility("database", Set.of("schema1"));
+
+        // "schema1" is platform hidden now
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .containsExactly("database/schema1");
+
+        // Add a new schema, and include the already-existing "schema1"
+        mAppSearchImpl.setSchema(
+                "database",
+                Set.of(
+                        new AppSearchSchema.Builder("schema1").build(),
+                        new AppSearchSchema.Builder("schema2").build()),
+                /*forceOverride=*/ false);
+
+        // Check that "schema1" is still platform hidden, but "schema2" is the default platform
+        // visible.
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .containsExactly("database/schema1");
     }
 
     @Test
@@ -340,35 +528,52 @@
         schemas.add(new AppSearchSchema.Builder("Email").build());
         schemas.add(new AppSearchSchema.Builder("Document").build());
         // Set schema Email and Document to AppSearch database1
-        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
 
-        // Create excepted schemaType proto.
-        SchemaProto exceptedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
-                .build();
+        // Create expected schemaType proto.
+        SchemaProto expectedProto =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("database1/Document"))
+                        .build();
 
         // Check both schema Email and Document saved correctly.
-        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
-                .containsExactlyElementsIn(exceptedProto.getTypesList());
+        List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+        expectedTypes.add(mVisibilitySchemaProto);
+        expectedTypes.addAll(expectedProto.getTypesList());
+        assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+                .containsExactlyElementsIn(expectedTypes);
 
-        final Set<AppSearchSchema> finalSchemas = Collections.singleton(new AppSearchSchema.Builder(
-                "Email").build());
+        final Set<AppSearchSchema> finalSchemas =
+                Collections.singleton(new AppSearchSchema.Builder("Email").build());
         // Check the incompatible error has been thrown.
-        AppSearchException e = expectThrows(AppSearchException.class, () ->
-                mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/false));
+        AppSearchException e =
+                expectThrows(
+                        AppSearchException.class,
+                        () ->
+                                mAppSearchImpl.setSchema(
+                                        "database1", finalSchemas, /*forceOverride=*/ false));
         assertThat(e).hasMessageThat().contains("Schema is incompatible");
         assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
 
         // ForceOverride to delete.
-        mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/true);
+        mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/ true);
 
         // Check Document schema is removed.
-        exceptedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .build();
-        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
-                .containsExactlyElementsIn(exceptedProto.getTypesList());
+        expectedProto =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                        .build();
+
+        expectedTypes = new ArrayList<>();
+        expectedTypes.add(mVisibilitySchemaProto);
+        expectedTypes.addAll(expectedProto.getTypesList());
+        assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+                .containsExactlyElementsIn(expectedTypes);
     }
 
     @Test
@@ -379,35 +584,156 @@
         schemas.add(new AppSearchSchema.Builder("Document").build());
 
         // Set schema Email and Document to AppSearch database1 and 2
-        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
-        mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
+        mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
 
-        // Create excepted schemaType proto.
-        SchemaProto exceptedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
-                .build();
+        // Create expected schemaType proto.
+        SchemaProto expectedProto =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("database1/Document"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("database2/Document"))
+                        .build();
 
         // Check Email and Document is saved in database 1 and 2 correctly.
-        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
-                .containsExactlyElementsIn(exceptedProto.getTypesList());
+        List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
+        expectedTypes.add(mVisibilitySchemaProto);
+        expectedTypes.addAll(expectedProto.getTypesList());
+        assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+                .containsExactlyElementsIn(expectedTypes);
 
         // Save only Email to database1 this time.
         schemas = Collections.singleton(new AppSearchSchema.Builder("Email").build());
-        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/true);
+        mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ true);
 
-        // Create excepted schemaType list, database 1 should only contain Email but database 2
+        // Create expected schemaType list, database 1 should only contain Email but database 2
         // remains in same.
-        exceptedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
-                .build();
+        expectedProto =
+                SchemaProto.newBuilder()
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+                        .addTypes(
+                                SchemaTypeConfigProto.newBuilder()
+                                        .setSchemaType("database2/Document"))
+                        .build();
 
         // Check nothing changed in database2.
-        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
-                .containsExactlyElementsIn(exceptedProto.getTypesList());
+        expectedTypes = new ArrayList<>();
+        expectedTypes.add(mVisibilitySchemaProto);
+        expectedTypes.addAll(expectedProto.getTypesList());
+        assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
+                .containsExactlyElementsIn(expectedTypes);
+    }
+
+    @Test
+    public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+                /*forceOverride=*/ false);
+        mAppSearchImpl.setVisibility("database", Set.of("schema1"));
+
+        // "schema1" is platform hidden now
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .containsExactly("database/schema1");
+
+        // Remove "schema1" by force overriding
+        mAppSearchImpl.setSchema("database", Collections.emptySet(), /*forceOverride=*/ true);
+
+        // Check that "schema1" is no longer considered platform hidden
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .isEmpty();
+
+        // Add "schema1" back, it gets default visibility settings which means it's not platform
+        // hidden.
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
+                /*forceOverride=*/ false);
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .isEmpty();
+    }
+
+    @Test
+    public void testSetVisibility_defaultPlatformVisible() throws Exception {
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+                /*forceOverride=*/ false);
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .isEmpty();
+    }
+
+    @Test
+    public void testSetVisibility_platformHidden() throws Exception {
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+                /*forceOverride=*/ false);
+        mAppSearchImpl.setVisibility("database", Set.of("Schema"));
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
+                .containsExactly("database/Schema");
+    }
+
+    @Test
+    public void testSetVisibility_unknownSchema() throws Exception {
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+                /*forceOverride=*/ false);
+
+        // We'll throw an exception if a client tries to set visibility on a schema we don't know
+        // about.
+        AppSearchException e =
+                expectThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.setVisibility("database", Set.of("UnknownSchema")));
+        assertThat(e).hasMessageThat().contains("Unknown schema(s)");
+    }
+
+    @Test
+    public void testHasSchemaType() throws Exception {
+        // Nothing exists yet
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isFalse();
+
+        mAppSearchImpl.setSchema(
+                "database",
+                Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
+                /*forceOverride=*/ false);
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isTrue();
+
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "UnknownSchema")).isFalse();
+    }
+
+    @Test
+    public void testGetDatabases() throws Exception {
+        // No client databases exist yet, but the VisibilityStore's does
+        assertThat(mAppSearchImpl.getDatabasesLocked())
+                .containsExactly(VisibilityStore.DATABASE_NAME);
+
+        // Has database1
+        mAppSearchImpl.setSchema(
+                "database1",
+                Collections.singleton(new AppSearchSchema.Builder("schema").build()),
+                /*forceOverride=*/ false);
+        assertThat(mAppSearchImpl.getDatabasesLocked())
+                .containsExactly(VisibilityStore.DATABASE_NAME, "database1");
+
+        // Has both databases
+        mAppSearchImpl.setSchema(
+                "database2",
+                Collections.singleton(new AppSearchSchema.Builder("schema").build()),
+                /*forceOverride=*/ false);
+        assertThat(mAppSearchImpl.getDatabasesLocked())
+                .containsExactly(VisibilityStore.DATABASE_NAME, "database1", "database2");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java
new file mode 100644
index 0000000..dfe2de6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/VisibilityStoreTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class VisibilityStoreTest {
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    private AppSearchImpl mAppSearchImpl;
+    private VisibilityStore mVisibilityStore;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
+        mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
+    }
+
+    @Test
+    public void testSetVisibility() throws Exception {
+        mVisibilityStore.setVisibility(
+                "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
+        assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+                .containsExactly("schema1", "schema2");
+
+        // New .setVisibility() call completely overrides previous visibility settings. So
+        // "schema1" isn't preserved.
+        mVisibilityStore.setVisibility(
+                "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema3"));
+        assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+                .containsExactly("schema1", "schema3");
+
+        mVisibilityStore.setVisibility(
+                "database", /*platformHiddenSchemas=*/ Collections.emptySet());
+        assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
+    }
+
+    @Test
+    public void testRemoveSchemas() throws Exception {
+        mVisibilityStore.setVisibility(
+                "database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
+
+        // Removed just schema1
+        mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema1"));
+        assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
+                .containsExactly("schema2");
+
+        // Removed everything now
+        mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema2"));
+        assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 85d4f01..98392a7 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -32,18 +32,18 @@
 import java.util.List;
 
 public class GenericDocumentToProtoConverterTest {
-    private static final byte[] BYTE_ARRAY_1 = new byte[]{(byte) 1, (byte) 2, (byte) 3};
-    private static final byte[] BYTE_ARRAY_2 = new byte[]{(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+    private static final byte[] BYTE_ARRAY_1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] BYTE_ARRAY_2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
     private static final GenericDocument DOCUMENT_PROPERTIES_1 =
             new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                    "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
-            .setCreationTimestampMillis(12345L)
-            .build();
+                            "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .setCreationTimestampMillis(12345L)
+                    .build();
     private static final GenericDocument DOCUMENT_PROPERTIES_2 =
             new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                    "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
-            .setCreationTimestampMillis(6789L)
-            .build();
+                            "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .setCreationTimestampMillis(6789L)
+                    .build();
 
     @Test
     public void testDocumentProtoConvert() {
@@ -63,32 +63,42 @@
                         .build();
 
         // Create the Document proto. Need to sort the property order by key.
-        DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
-                .setUri("uri1")
-                .setSchema("schemaType1")
-                .setCreationTimestampMs(5L)
-                .setScore(1)
-                .setTtlMs(1L)
-                .setNamespace("namespace");
+        DocumentProto.Builder documentProtoBuilder =
+                DocumentProto.newBuilder()
+                        .setUri("uri1")
+                        .setSchema("schemaType1")
+                        .setCreationTimestampMs(5L)
+                        .setScore(1)
+                        .setTtlMs(1L)
+                        .setNamespace("namespace");
         HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
-        propertyProtoMap.put("longKey1",
-                PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
-        propertyProtoMap.put("doubleKey1",
+        propertyProtoMap.put(
+                "longKey1", PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+        propertyProtoMap.put(
+                "doubleKey1",
                 PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
-        propertyProtoMap.put("booleanKey1",
+        propertyProtoMap.put(
+                "booleanKey1",
                 PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
-        propertyProtoMap.put("stringKey1",
+        propertyProtoMap.put(
+                "stringKey1",
                 PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
-        propertyProtoMap.put("byteKey1",
-                PropertyProto.newBuilder().setName("byteKey1")
+        propertyProtoMap.put(
+                "byteKey1",
+                PropertyProto.newBuilder()
+                        .setName("byteKey1")
                         .addBytesValues(ByteString.copyFrom(BYTE_ARRAY_1))
                         .addBytesValues(ByteString.copyFrom(BYTE_ARRAY_2)));
-        propertyProtoMap.put("documentKey1",
-                PropertyProto.newBuilder().setName("documentKey1")
+        propertyProtoMap.put(
+                "documentKey1",
+                PropertyProto.newBuilder()
+                        .setName("documentKey1")
                         .addDocumentValues(
                                 GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_1)));
-        propertyProtoMap.put("documentKey2",
-                PropertyProto.newBuilder().setName("documentKey2")
+        propertyProtoMap.put(
+                "documentKey2",
+                PropertyProto.newBuilder()
+                        .setName("documentKey2")
                         .addDocumentValues(
                                 GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_2)));
         List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
@@ -97,8 +107,7 @@
             documentProtoBuilder.addProperties(propertyProtoMap.get(key));
         }
         DocumentProto documentProto = documentProtoBuilder.build();
-        assertThat(GenericDocumentToProtoConverter.convert(document))
-                .isEqualTo(documentProto);
+        assertThat(GenericDocumentToProtoConverter.convert(document)).isEqualTo(documentProto);
         assertThat(document).isEqualTo(GenericDocumentToProtoConverter.convert(documentProto));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
index 7336c3c..dedfca4 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
@@ -20,9 +20,9 @@
 
 import android.app.appsearch.AppSearchSchema;
 
-import com.android.server.appsearch.proto.IndexingConfig;
 import com.android.server.appsearch.proto.PropertyConfigProto;
 import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.StringIndexingConfig;
 import com.android.server.appsearch.proto.TermMatchType;
 
 import org.junit.Test;
@@ -30,84 +30,126 @@
 public class SchemaToProtoConverterTest {
     @Test
     public void testGetProto_Email() {
-        AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
-                .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
-                        .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
-                        .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).build();
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(
+                                                AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.PropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("body")
+                                        .setDataType(
+                                                AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.PropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
 
-        SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder()
-                .setSchemaType("Email")
-                .addProperties(PropertyConfigProto.newBuilder()
-                        .setPropertyName("subject")
-                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                        .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                        .setIndexingConfig(
-                                com.android.server.appsearch.proto.IndexingConfig.newBuilder()
-                                        .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
-                                        .setTermMatchType(TermMatchType.Code.PREFIX)
-                        )
-                ).addProperties(PropertyConfigProto.newBuilder()
-                        .setPropertyName("body")
-                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                        .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                        .setIndexingConfig(
-                                com.android.server.appsearch.proto.IndexingConfig.newBuilder()
-                                        .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
-                                        .setTermMatchType(TermMatchType.Code.PREFIX)
-                        )
-                ).build();
+        SchemaTypeConfigProto expectedEmailProto =
+                SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("Email")
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("subject")
+                                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                        .setStringIndexingConfig(
+                                                StringIndexingConfig.newBuilder()
+                                                        .setTokenizerType(
+                                                                StringIndexingConfig.TokenizerType
+                                                                        .Code.PLAIN)
+                                                        .setTermMatchType(
+                                                                TermMatchType.Code.PREFIX)))
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("body")
+                                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                        .setStringIndexingConfig(
+                                                StringIndexingConfig.newBuilder()
+                                                        .setTokenizerType(
+                                                                StringIndexingConfig.TokenizerType
+                                                                        .Code.PLAIN)
+                                                        .setTermMatchType(
+                                                                TermMatchType.Code.PREFIX)))
+                        .build();
 
         assertThat(SchemaToProtoConverter.convert(emailSchema)).isEqualTo(expectedEmailProto);
     }
 
     @Test
     public void testGetProto_MusicRecording() {
-        AppSearchSchema musicRecordingSchema = new AppSearchSchema.Builder("MusicRecording")
-                .addProperty(new AppSearchSchema.PropertyConfig.Builder("artist")
-                        .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-                        .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).addProperty(new AppSearchSchema.PropertyConfig.Builder("pubDate")
-                        .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
-                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
-                        .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
-                        .build()
-                ).build();
+        AppSearchSchema musicRecordingSchema =
+                new AppSearchSchema.Builder("MusicRecording")
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("artist")
+                                        .setDataType(
+                                                AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+                                        .setIndexingType(
+                                                AppSearchSchema.PropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("pubDate")
+                                        .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
+                                        .setTokenizerType(
+                                                AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
+                                        .build())
+                        .build();
 
-        SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder()
-                .setSchemaType("MusicRecording")
-                .addProperties(PropertyConfigProto.newBuilder()
-                        .setPropertyName("artist")
-                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                        .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
-                        .setIndexingConfig(
-                                com.android.server.appsearch.proto.IndexingConfig.newBuilder()
-                                        .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
-                                        .setTermMatchType(TermMatchType.Code.PREFIX)
-                        )
-                ).addProperties(PropertyConfigProto.newBuilder()
-                        .setPropertyName("pubDate")
-                        .setDataType(PropertyConfigProto.DataType.Code.INT64)
-                        .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                        .setIndexingConfig(
-                                com.android.server.appsearch.proto.IndexingConfig.newBuilder()
-                                        .setTokenizerType(IndexingConfig.TokenizerType.Code.NONE)
-                                        .setTermMatchType(TermMatchType.Code.UNKNOWN)
-                        )
-                ).build();
+        SchemaTypeConfigProto expectedMusicRecordingProto =
+                SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("MusicRecording")
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("artist")
+                                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.REPEATED)
+                                        .setStringIndexingConfig(
+                                                StringIndexingConfig.newBuilder()
+                                                        .setTokenizerType(
+                                                                StringIndexingConfig.TokenizerType
+                                                                        .Code.PLAIN)
+                                                        .setTermMatchType(
+                                                                TermMatchType.Code.PREFIX)))
+                        .addProperties(
+                                PropertyConfigProto.newBuilder()
+                                        .setPropertyName("pubDate")
+                                        .setDataType(PropertyConfigProto.DataType.Code.INT64)
+                                        .setCardinality(
+                                                PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                        .setStringIndexingConfig(
+                                                StringIndexingConfig.newBuilder()
+                                                        .setTokenizerType(
+                                                                StringIndexingConfig.TokenizerType
+                                                                        .Code.NONE)
+                                                        .setTermMatchType(
+                                                                TermMatchType.Code.UNKNOWN)))
+                        .build();
 
         assertThat(SchemaToProtoConverter.convert(musicRecordingSchema))
                 .isEqualTo(expectedMusicRecordingProto);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index 2e9286c..518f532 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -36,9 +36,10 @@
     public void testSingleStringSnippet() {
 
         final String propertyKeyString = "content";
-        final String propertyValueString = "A commonly used fake word is foo.\n"
-                + "   Another nonsense word that’s used a lot\n"
-                + "   is bar.\n";
+        final String propertyValueString =
+                "A commonly used fake word is foo.\n"
+                        + "   Another nonsense word that’s used a lot\n"
+                        + "   is bar.\n";
         final String uri = "uri1";
         final String schemaType = "schema1";
         final String searchWord = "foo";
@@ -46,34 +47,39 @@
         final String window = "is foo";
 
         // Building the SearchResult received from query.
-        PropertyProto property = PropertyProto.newBuilder()
-                .setName(propertyKeyString)
-                .addStringValues(propertyValueString)
-                .build();
-        DocumentProto documentProto = DocumentProto.newBuilder()
-                .setUri(uri)
-                .setSchema(schemaType)
-                .addProperties(property)
-                .build();
-        SnippetProto snippetProto = SnippetProto.newBuilder()
-                .addEntries(SnippetProto.EntryProto.newBuilder()
-                        .setPropertyName(propertyKeyString)
-                        .addSnippetMatches(SnippetMatchProto.newBuilder()
-                                .setValuesIndex(0)
-                                .setExactMatchPosition(29)
-                                .setExactMatchBytes(3)
-                                .setWindowPosition(26)
-                                .setWindowBytes(6)
-                                .build())
-                        .build())
-                .build();
-        SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
-                .setDocument(documentProto)
-                .setSnippet(snippetProto)
-                .build();
-        SearchResultProto searchResultProto = SearchResultProto.newBuilder()
-                .addResults(resultProto)
-                .build();
+        PropertyProto property =
+                PropertyProto.newBuilder()
+                        .setName(propertyKeyString)
+                        .addStringValues(propertyValueString)
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri(uri)
+                        .setSchema(schemaType)
+                        .addProperties(property)
+                        .build();
+        SnippetProto snippetProto =
+                SnippetProto.newBuilder()
+                        .addEntries(
+                                SnippetProto.EntryProto.newBuilder()
+                                        .setPropertyName(propertyKeyString)
+                                        .addSnippetMatches(
+                                                SnippetMatchProto.newBuilder()
+                                                        .setValuesIndex(0)
+                                                        .setExactMatchPosition(29)
+                                                        .setExactMatchBytes(3)
+                                                        .setWindowPosition(26)
+                                                        .setWindowBytes(6)
+                                                        .build())
+                                        .build())
+                        .build();
+        SearchResultProto.ResultProto resultProto =
+                SearchResultProto.ResultProto.newBuilder()
+                        .setDocument(documentProto)
+                        .setSnippet(snippetProto)
+                        .build();
+        SearchResultProto searchResultProto =
+                SearchResultProto.newBuilder().addResults(resultProto).build();
 
         // Making ResultReader and getting Snippet values.
         SearchResultPage searchResultPage =
@@ -83,11 +89,11 @@
             assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
             assertThat(match.getFullText()).isEqualTo(propertyValueString);
             assertThat(match.getExactMatch()).isEqualTo(exactMatch);
-            assertThat(match.getExactMatchPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/29, /*upper=*/32));
+            assertThat(match.getExactMatchPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
             assertThat(match.getFullText()).isEqualTo(propertyValueString);
-            assertThat(match.getSnippetPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/26, /*upper=*/32));
+            assertThat(match.getSnippetPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 32));
             assertThat(match.getSnippet()).isEqualTo(window);
         }
     }
@@ -97,9 +103,10 @@
     public void testNoSnippets() throws Exception {
 
         final String propertyKeyString = "content";
-        final String propertyValueString = "A commonly used fake word is foo.\n"
-                + "   Another nonsense word that’s used a lot\n"
-                + "   is bar.\n";
+        final String propertyValueString =
+                "A commonly used fake word is foo.\n"
+                        + "   Another nonsense word that’s used a lot\n"
+                        + "   is bar.\n";
         final String uri = "uri1";
         final String schemaType = "schema1";
         final String searchWord = "foo";
@@ -107,21 +114,21 @@
         final String window = "is foo";
 
         // Building the SearchResult received from query.
-        PropertyProto property = PropertyProto.newBuilder()
-                .setName(propertyKeyString)
-                .addStringValues(propertyValueString)
-                .build();
-        DocumentProto documentProto = DocumentProto.newBuilder()
-                .setUri(uri)
-                .setSchema(schemaType)
-                .addProperties(property)
-                .build();
-        SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
-                .setDocument(documentProto)
-                .build();
-        SearchResultProto searchResultProto = SearchResultProto.newBuilder()
-                .addResults(resultProto)
-                .build();
+        PropertyProto property =
+                PropertyProto.newBuilder()
+                        .setName(propertyKeyString)
+                        .addStringValues(propertyValueString)
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri(uri)
+                        .setSchema(schemaType)
+                        .addProperties(property)
+                        .build();
+        SearchResultProto.ResultProto resultProto =
+                SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
+        SearchResultProto searchResultProto =
+                SearchResultProto.newBuilder().addResults(resultProto).build();
 
         SearchResultPage searchResultPage =
                 SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
@@ -135,54 +142,57 @@
         final String searchWord = "Test";
 
         // Building the SearchResult received from query.
-        PropertyProto property1 = PropertyProto.newBuilder()
-                .setName("sender.name")
-                .addStringValues("Test Name Jr.")
-                .build();
-        PropertyProto property2 = PropertyProto.newBuilder()
-                .setName("sender.email")
-                .addStringValues("TestNameJr@gmail.com")
-                .build();
-        DocumentProto documentProto = DocumentProto.newBuilder()
-                .setUri("uri1")
-                .setSchema("schema1")
-                .addProperties(property1)
-                .addProperties(property2)
-                .build();
-        SnippetProto snippetProto = SnippetProto.newBuilder()
-                .addEntries(
-                        SnippetProto.EntryProto.newBuilder()
-                                .setPropertyName("sender.name")
-                                .addSnippetMatches(
-                                        SnippetMatchProto.newBuilder()
-                                                .setValuesIndex(0)
-                                                .setExactMatchPosition(0)
-                                                .setExactMatchBytes(4)
-                                                .setWindowPosition(0)
-                                                .setWindowBytes(9)
-                                                .build())
-                                .build())
-                .addEntries(
-                        SnippetProto.EntryProto.newBuilder()
-                                .setPropertyName("sender.email")
-                                .addSnippetMatches(
-                                        SnippetMatchProto.newBuilder()
-                                                .setValuesIndex(0)
-                                                .setExactMatchPosition(0)
-                                                .setExactMatchBytes(20)
-                                                .setWindowPosition(0)
-                                                .setWindowBytes(20)
-                                                .build())
-                                .build()
-                )
-                .build();
-        SearchResultProto.ResultProto resultProto = SearchResultProto.ResultProto.newBuilder()
-                .setDocument(documentProto)
-                .setSnippet(snippetProto)
-                .build();
-        SearchResultProto searchResultProto = SearchResultProto.newBuilder()
-                .addResults(resultProto)
-                .build();
+        PropertyProto property1 =
+                PropertyProto.newBuilder()
+                        .setName("sender.name")
+                        .addStringValues("Test Name Jr.")
+                        .build();
+        PropertyProto property2 =
+                PropertyProto.newBuilder()
+                        .setName("sender.email")
+                        .addStringValues("TestNameJr@gmail.com")
+                        .build();
+        DocumentProto documentProto =
+                DocumentProto.newBuilder()
+                        .setUri("uri1")
+                        .setSchema("schema1")
+                        .addProperties(property1)
+                        .addProperties(property2)
+                        .build();
+        SnippetProto snippetProto =
+                SnippetProto.newBuilder()
+                        .addEntries(
+                                SnippetProto.EntryProto.newBuilder()
+                                        .setPropertyName("sender.name")
+                                        .addSnippetMatches(
+                                                SnippetMatchProto.newBuilder()
+                                                        .setValuesIndex(0)
+                                                        .setExactMatchPosition(0)
+                                                        .setExactMatchBytes(4)
+                                                        .setWindowPosition(0)
+                                                        .setWindowBytes(9)
+                                                        .build())
+                                        .build())
+                        .addEntries(
+                                SnippetProto.EntryProto.newBuilder()
+                                        .setPropertyName("sender.email")
+                                        .addSnippetMatches(
+                                                SnippetMatchProto.newBuilder()
+                                                        .setValuesIndex(0)
+                                                        .setExactMatchPosition(0)
+                                                        .setExactMatchBytes(20)
+                                                        .setWindowPosition(0)
+                                                        .setWindowBytes(20)
+                                                        .build())
+                                        .build())
+                        .build();
+        SearchResultProto.ResultProto resultProto =
+                SearchResultProto.ResultProto.newBuilder()
+                        .setDocument(documentProto)
+                        .setSnippet(snippetProto)
+                        .build();
+        SearchResultProto searchResultProto =
+                SearchResultProto.newBuilder().addResults(resultProto).build();
 
         // Making ResultReader and getting Snippet values.
         SearchResultPage searchResultPage =
@@ -192,21 +202,21 @@
             SearchResult.MatchInfo match1 = result.getMatches().get(0);
             assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
             assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
-            assertThat(match1.getExactMatchPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0, /*upper=*/4));
+            assertThat(match1.getExactMatchPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 4));
             assertThat(match1.getExactMatch()).isEqualTo("Test");
-            assertThat(match1.getSnippetPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0, /*upper=*/9));
+            assertThat(match1.getSnippetPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 9));
             assertThat(match1.getSnippet()).isEqualTo("Test Name");
 
             SearchResult.MatchInfo match2 = result.getMatches().get(1);
             assertThat(match2.getPropertyPath()).isEqualTo("sender.email");
             assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
-            assertThat(match2.getExactMatchPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0, /*upper=*/20));
+            assertThat(match2.getExactMatchPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
             assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com");
-            assertThat(match2.getSnippetPosition()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0, /*upper=*/20));
+            assertThat(match2.getSnippetPosition())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
             assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com");
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index ce3b8d6..dfeed13 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1248,4 +1248,91 @@
         assertThat(mHdmiControlService.getActiveSource().getPhysicalAddress()).isEqualTo(
                 externalDevice.getPhysicalAddress());
     }
+
+    @Test
+    public void queryDisplayStatus() {
+        mHdmiControlService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int result) {
+            }
+        });
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackLogicalAddress, Constants.ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_ToTv_TvStatusOn() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+        mStandby = false;
+        mHdmiControlService.toggleAndFollowTvPower();
+        HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+                mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+                mPlaybackLogicalAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+        assertThat(mStandby).isTrue();
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_Broadcast_TvStatusOn() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+        mStandby = false;
+        mHdmiControlService.toggleAndFollowTvPower();
+        HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+                mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
+                mPlaybackLogicalAddress, ADDR_BROADCAST);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+        assertThat(mStandby).isTrue();
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_TvStatusStandby() {
+        mStandby = false;
+        mHdmiControlService.toggleAndFollowTvPower();
+        HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+                mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+                ADDR_TV);
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void toggleAndFollowTvPower_TvStatusUnknown() {
+        mStandby = false;
+        mHdmiControlService.toggleAndFollowTvPower();
+        HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
+                mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, Constants.ADDR_TV,
+                HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
+        HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, Constants.ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed);
+        assertThat(mNativeWrapper.getResultMessages()).contains(userControlReleased);
+        assertThat(mStandby).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index 6f62014..6496264 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -22,6 +22,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.platform.test.annotations.Presubmit;
 
@@ -103,7 +104,7 @@
     @Test
     public void buildReportFeatures_basicTv_1_4() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_1_4,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
 
@@ -113,7 +114,7 @@
     @Test
     public void buildReportFeatures_basicPlayback_1_4() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
-                Constants.VERSION_1_4,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
 
@@ -123,7 +124,7 @@
     @Test
     public void buildReportFeatures_basicPlaybackAudioSystem_1_4() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1,
-                Constants.VERSION_1_4,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_b,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
                         HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
@@ -134,7 +135,7 @@
     @Test
     public void buildReportFeatures_basicTv_2_0() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_2_0,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList());
 
@@ -144,7 +145,7 @@
     @Test
     public void buildReportFeatures_remoteControlTv_2_0() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_2_0,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), Collections.emptyList());
 
@@ -154,7 +155,7 @@
     @Test
     public void buildReportFeatures_remoteControlPlayback_2_0() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_2_0,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
                 Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
                         Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), Collections.emptyList());
@@ -165,7 +166,7 @@
     @Test
     public void buildReportFeatures_deviceFeaturesTv_2_0() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_2_0,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
                 Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
                 Lists.newArrayList(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN));
@@ -176,7 +177,7 @@
     @Test
     public void buildReportFeatures_deviceFeaturesPlayback_2_0() {
         HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV,
-                Constants.VERSION_2_0,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE,
                 Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
                         Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU),
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index f3a4366..a05cbb4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -67,8 +67,8 @@
     public void isValid_reportPowerStatus() {
         assertMessageValidity("04:90:00").isEqualTo(OK);
         assertMessageValidity("04:90:03:05").isEqualTo(OK);
+        assertMessageValidity("0F:90:00").isEqualTo(OK);
 
-        assertMessageValidity("0F:90:00").isEqualTo(ERROR_DESTINATION);
         assertMessageValidity("F0:90").isEqualTo(ERROR_SOURCE);
         assertMessageValidity("04:90").isEqualTo(ERROR_PARAMETER_SHORT);
         assertMessageValidity("04:90:04").isEqualTo(ERROR_PARAMETER);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
new file mode 100644
index 0000000..3cc7c6b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecPowerStatusController} class. */
+public class HdmiCecPowerStatusControllerTest {
+
+    public static final int[] ARRAY_POWER_STATUS = new int[]{HdmiControlManager.POWER_STATUS_ON,
+            HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
+            HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
+            HdmiControlManager.POWER_STATUS_STANDBY};
+    private HdmiCecPowerStatusController mHdmiCecPowerStatusController;
+    private FakeNativeWrapper mNativeWrapper;
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_b;
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+    private HdmiControlService mHdmiControlService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        Looper myLooper = mTestLooper.getLooper();
+        PowerManager powerManager = new PowerManager(contextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(myLooper));
+        when(contextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(contextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(contextSpy);
+
+        mHdmiControlService = new HdmiControlService(contextSpy) {
+            @Override
+            boolean isControlEnabled() {
+                return true;
+            }
+
+            @Override
+            boolean isPlaybackDevice() {
+                return true;
+            }
+
+            @Override
+            void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+
+            @Override
+            int getCecVersion() {
+                return mHdmiCecVersion;
+            }
+
+            @Override
+            boolean isPowerStandby() {
+                return false;
+            }
+
+            @Override
+            HdmiCecConfig getHdmiCecConfig() {
+                return hdmiCecConfig;
+            }
+        };
+        mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        HdmiCecLocalDevicePlayback hdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(
+                mHdmiControlService);
+        hdmiCecLocalDevicePlayback.init();
+        mHdmiControlService.setIoLooper(myLooper);
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mLocalDevices.add(hdmiCecLocalDevicePlayback);
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        mHdmiControlService.initService();
+        mHdmiControlService.getHdmiCecNetwork().initPortInfo();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mNativeWrapper.setPhysicalAddress(0x2000);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecPowerStatusController = new HdmiCecPowerStatusController(mHdmiControlService);
+        mNativeWrapper.clearResultMessages();
+    }
+
+    @Test
+    public void setPowerStatus() {
+        for (int status : ARRAY_POWER_STATUS) {
+            mHdmiCecPowerStatusController.setPowerStatus(status);
+            assertThat(mHdmiCecPowerStatusController.getPowerStatus()).isEqualTo(status);
+        }
+    }
+
+    @Test
+    public void isPowerStatusOn() {
+        for (int status : ARRAY_POWER_STATUS) {
+            mHdmiCecPowerStatusController.setPowerStatus(status);
+            assertThat(mHdmiCecPowerStatusController.isPowerStatusOn()).isEqualTo(
+                    HdmiControlManager.POWER_STATUS_ON == status);
+        }
+    }
+
+    @Test
+    public void isPowerStatusStandby() {
+        for (int status : ARRAY_POWER_STATUS) {
+            mHdmiCecPowerStatusController.setPowerStatus(status);
+            assertThat(mHdmiCecPowerStatusController.isPowerStatusStandby()).isEqualTo(
+                    HdmiControlManager.POWER_STATUS_STANDBY == status);
+        }
+    }
+
+    @Test
+    public void isPowerStatusTransientToOn() {
+        for (int status : ARRAY_POWER_STATUS) {
+            mHdmiCecPowerStatusController.setPowerStatus(status);
+            assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToOn()).isEqualTo(
+                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON == status);
+        }
+    }
+
+    @Test
+    public void isPowerStatusTransientToStandby() {
+        for (int status : ARRAY_POWER_STATUS) {
+            mHdmiCecPowerStatusController.setPowerStatus(status);
+            assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToStandby()).isEqualTo(
+                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY == status);
+        }
+    }
+
+    @Test
+    public void setPowerStatus_doesntSendBroadcast_1_4() {
+        mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_ON);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+    }
+
+    @Test
+    public void setPowerStatus_transient_doesntSendBroadcast_1_4() {
+        mHdmiCecPowerStatusController.setPowerStatus(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+    }
+
+    @Test
+    public void setPowerStatus_fast_transient_doesntSendBroadcast_1_4() {
+        mHdmiCecPowerStatusController.setPowerStatus(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+    }
+
+    @Test
+    public void setPowerStatus_sendsBroadcast_2_0() {
+        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+        mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_ON);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+    }
+
+    @Test
+    public void setPowerStatus_transient_sendsBroadcast_2_0() {
+        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+        mHdmiCecPowerStatusController.setPowerStatus(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+    }
+
+    @Test
+    public void setPowerStatus_fast_transient_doesntSendBroadcast_2_0() {
+        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+
+        mHdmiCecPowerStatusController.setPowerStatus(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 2e4bed9..9d767cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -243,6 +243,50 @@
     }
 
     @Test
+    public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_b);
+
+        mHdmiControlService.setControlEnabled(true);
+        mNativeWrapper.clearResultMessages();
+
+        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_STANDBY);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
+    }
+
+    @Test
+    public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+
+        mHdmiControlService.setControlEnabled(true);
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
+                HdmiControlManager.POWER_STATUS_STANDBY);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
+    }
+
+    @Test
     public void setAndGetCecVolumeControlEnabled_isApi() {
         mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
         assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
@@ -470,7 +514,7 @@
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
-                Constants.ADDR_PLAYBACK_1, Constants.VERSION_2_0,
+                Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
                 Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
                 mMyPlaybackDevice.getRcProfile(), mMyPlaybackDevice.getRcFeatures(),
                 mMyPlaybackDevice.getDeviceFeatures());
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 360e11c..e46ab6b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -586,7 +586,7 @@
 
         @Override
         boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
-            return true;
+            return mInjectCheckAccessShortcutsPermission;
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index c7a05ba..194ae05 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -490,6 +490,7 @@
         mManager.pushDynamicShortcut(s8);
         assertEquals(4, getCallerShortcut("s8").getRank());
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s8"), HANDLE_USER_0,
                     CACHE_OWNER_0);
         });
@@ -1456,6 +1457,7 @@
 
         // Cache 1 and 2
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"),
                     HANDLE_USER_0, CACHE_OWNER_0);
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"),
@@ -1538,6 +1540,7 @@
 
         // Cache some, but non long lived shortcuts will be ignored.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
                     HANDLE_USER_0, CACHE_OWNER_0);
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1597,6 +1600,48 @@
                 "s2");
     }
 
+    public void testCachedShortcuts_accessShortcutsPermission() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
+                    makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+                    makeLongLivedShortcut("s4"))));
+        });
+
+        // s1 is not long lived and will be ignored.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = false;
+            assertExpectException(
+                    SecurityException.class, "Caller can't access shortcut information", () -> {
+                        mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"),
+                                HANDLE_USER_0, CACHE_OWNER_0);
+                    });
+            // Give ACCESS_SHORTCUTS permission to LAUNCHER_1
+            mInjectCheckAccessShortcutsPermission = true;
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"),
+                    HANDLE_USER_0, CACHE_OWNER_0);
+        });
+
+        setCaller(CALLING_PACKAGE_1);
+
+        // Get cached shortcuts
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s2", "s3");
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = false;
+            assertExpectException(
+                    SecurityException.class, "Caller can't access shortcut information", () -> {
+                        mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+                                HANDLE_USER_0, CACHE_OWNER_0);
+                    });
+            // Give ACCESS_SHORTCUTS permission to LAUNCHER_1
+            mInjectCheckAccessShortcutsPermission = true;
+            mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+                    HANDLE_USER_0, CACHE_OWNER_0);
+        });
+
+        assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3");
+    }
+
     public void testCachedShortcuts_canPassShortcutLimit() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
@@ -1609,6 +1654,7 @@
 
         // Cache All
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
                     HANDLE_USER_0, CACHE_OWNER_0);
         });
@@ -1808,6 +1854,7 @@
         setCaller(LAUNCHER_1);
 
         // Cache some shortcuts. Only long lived shortcuts can get cached.
+        mInjectCheckAccessShortcutsPermission = true;
         mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser(),
                 CACHE_OWNER_0);
         mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser(),
@@ -2009,6 +2056,53 @@
         });
     }
 
+    public void testGetShortcuts_personsFlag() {
+        ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id")
+                .setShortLabel("label")
+                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setPerson(makePerson("person", "personKey", "personUri"))
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .build();
+
+        setCaller(CALLING_PACKAGE_1);
+        assertTrue(mManager.setDynamicShortcuts(list(s)));
+
+        setCaller(LAUNCHER_1);
+
+        assertNull(mLauncherApps.getShortcuts(buildQuery(
+                /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+                ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                getCallingUser()).get(0).getPersons());
+
+        assertNull(mLauncherApps.getShortcuts(buildQuery(
+                /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+                ShortcutQuery.FLAG_MATCH_DYNAMIC),
+                getCallingUser()).get(0).getPersons());
+
+        // Using FLAG_GET_PERSONS_DATA should fail without permission
+        mInjectCheckAccessShortcutsPermission = false;
+        assertExpectException(
+                SecurityException.class, "Caller can't access shortcut information", () -> {
+                    mLauncherApps.getShortcuts(buildQuery(
+                            /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+                            ShortcutQuery.FLAG_MATCH_DYNAMIC
+                                    | ShortcutQuery.FLAG_GET_PERSONS_DATA),
+                            getCallingUser());
+                });
+
+        mInjectCheckAccessShortcutsPermission = true;
+        assertEquals("person", mLauncherApps.getShortcuts(buildQuery(
+                /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+                ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_PERSONS_DATA),
+                getCallingUser()).get(0).getPersons()[0].getName());
+
+        assertNull(mLauncherApps.getShortcuts(buildQuery(
+                /* time =*/ 0, CALLING_PACKAGE_1, /* activity =*/ null,
+                ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_GET_PERSONS_DATA
+                        | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                getCallingUser()).get(0).getPersons());
+    }
+
     // TODO resource
     public void testGetShortcutInfo() {
         // Create shortcuts.
@@ -8740,6 +8834,7 @@
         assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0,
                 filter_any));
 
+        mInjectCheckAccessShortcutsPermission = true;
         mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
                 CACHE_OWNER_0);
         mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
index 6a2b8e0..c8a4052 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
@@ -117,6 +117,7 @@
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
         });
@@ -216,6 +217,7 @@
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
                     mTestLooper.getNewExecutor());
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
@@ -242,6 +244,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -274,6 +277,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -301,6 +305,7 @@
         });
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
@@ -500,6 +505,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
@@ -559,6 +565,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -596,6 +603,7 @@
         });
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -664,6 +672,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -731,6 +740,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
@@ -799,6 +809,7 @@
 
         ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
             mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
                     CACHE_OWNER_0);
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
new file mode 100644
index 0000000..92942bb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+
+import static android.content.Context.SENSOR_SERVICE;
+
+import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManagerInternal;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceStateProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DeviceStateProviderImpl}.
+ * <p/>
+ * Run with <code>atest DeviceStateProviderImplTest</code>.
+ */
+public final class DeviceStateProviderImplTest {
+    private final ArgumentCaptor<int[]> mIntArrayCaptor = ArgumentCaptor.forClass(int[].class);
+    private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+
+    private Context mContext;
+    private SensorManager mSensorManager;
+
+    @Before
+    public void setup() {
+        LocalServices.addService(InputManagerInternal.class, mock(InputManagerInternal.class));
+        mContext = mock(Context.class);
+        mSensorManager = mock(SensorManager.class);
+        when(mContext.getSystemServiceName(eq(SensorManager.class))).thenReturn(SENSOR_SERVICE);
+        when(mContext.getSystemService(eq(SENSOR_SERVICE))).thenReturn(mSensorManager);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+    }
+
+    @Test
+    public void create_noConfig() {
+        assertDefaultProviderValues(null);
+    }
+
+    @Test
+    public void create_emptyFile() {
+        String configString = "";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+        assertDefaultProviderValues(config);
+    }
+
+    @Test
+    public void create_emptyConfig() {
+        String configString = "<device-state-config></device-state-config>";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+        assertDefaultProviderValues(config);
+    }
+
+    @Test
+    public void create_invalidConfig() {
+        String configString = "<device-state-config>\n"
+                + "    </device-state>\n"
+                + "</device-state-config>\n";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+
+        assertDefaultProviderValues(config);
+    }
+
+    private void assertDefaultProviderValues(
+            @Nullable DeviceStateProviderImpl.ReadableConfig config) {
+        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+                config);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        assertArrayEquals(new int[] { DEFAULT_DEVICE_STATE }, mIntArrayCaptor.getValue());
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(DEFAULT_DEVICE_STATE, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void create_lidSwitch() {
+        String configString = "<device-state-config>\n"
+                + "    <device-state>\n"
+                + "        <identifier>1</identifier>\n"
+                + "        <conditions>\n"
+                + "            <lid-switch>\n"
+                + "                <open>true</open>\n"
+                + "            </lid-switch>\n"
+                + "        </conditions>\n"
+                + "    </device-state>\n"
+                + "    <device-state>\n"
+                + "        <identifier>2</identifier>\n"
+                + "        <conditions>\n"
+                + "            <lid-switch>\n"
+                + "                <open>false</open>\n"
+                + "            </lid-switch>\n"
+                + "        </conditions>\n"
+                + "    </device-state>\n"
+                + "</device-state-config>\n";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+                config);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        provider.notifyLidSwitchChanged(0, true /* lidOpen */);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void create_sensor() throws Exception {
+        Sensor sensor = newSensor("sensor", Sensor.TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(eq(sensor.getType()))).thenReturn(List.of(sensor));
+
+        String configString = "<device-state-config>\n"
+                + "    <device-state>\n"
+                + "        <identifier>1</identifier>\n"
+                + "        <conditions>\n"
+                + "            <sensor>\n"
+                + "                <name>" + sensor.getName() + "</name>\n"
+                + "                <type>" + sensor.getType() + "</type>\n"
+                + "                <value>\n"
+                + "                    <max>90</max>\n"
+                + "                </value>\n"
+                + "            </sensor>\n"
+                + "        </conditions>\n"
+                + "    </device-state>\n"
+                + "    <device-state>\n"
+                + "        <identifier>2</identifier>\n"
+                + "        <conditions>\n"
+                + "            <sensor>\n"
+                + "                <name>" + sensor.getName() + "</name>\n"
+                + "                <type>" + sensor.getType() + "</type>\n"
+                + "                <value>\n"
+                + "                    <min-inclusive>90</min-inclusive>\n"
+                + "                    <max>180</max>\n"
+                + "                </value>\n"
+                + "            </sensor>\n"
+                + "        </conditions>\n"
+                + "    </device-state>\n"
+                + "    <device-state>\n"
+                + "        <identifier>3</identifier>\n"
+                + "        <conditions>\n"
+                + "            <sensor>\n"
+                + "                <name>" + sensor.getName() + "</name>\n"
+                + "                <type>" + sensor.getType() + "</type>\n"
+                + "                <value>\n"
+                + "                    <min-inclusive>180</min-inclusive>\n"
+                + "                </value>\n"
+                + "            </sensor>\n"
+                + "        </conditions>\n"
+                + "    </device-state>\n"
+                + "</device-state-config>\n";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+                config);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        assertArrayEquals(new int[] { 1, 2, 3 }, mIntArrayCaptor.getValue());
+
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        SensorEvent event0 = mock(SensorEvent.class);
+        event0.sensor = sensor;
+        FieldSetter.setField(event0, event0.getClass().getField("values"), new float[] { 180 });
+
+        provider.onSensorChanged(event0);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(3, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        SensorEvent event1 = mock(SensorEvent.class);
+        event1.sensor = sensor;
+        FieldSetter.setField(event1, event1.getClass().getField("values"), new float[] { 90 });
+
+        provider.onSensorChanged(event1);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        SensorEvent event2 = mock(SensorEvent.class);
+        event2.sensor = sensor;
+        FieldSetter.setField(event2, event2.getClass().getField("values"), new float[] { 0 });
+
+        provider.onSensorChanged(event2);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(1, mIntegerCaptor.getValue().intValue());
+    }
+
+    private static Sensor newSensor(String name, int type) throws Exception {
+        Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
+        constructor.setAccessible(true);
+
+        Sensor sensor = constructor.newInstance();
+        FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mName"), name);
+        FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mType"), type);
+        return sensor;
+    }
+
+    private static final class TestReadableConfig implements
+            DeviceStateProviderImpl.ReadableConfig {
+        private final byte[] mData;
+
+        TestReadableConfig(String configFileData) {
+            mData = configFileData.getBytes();
+        }
+
+        @NonNull
+        @Override
+        public InputStream openRead() throws IOException {
+            return new ByteArrayInputStream(mData);
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 9766cb5..00f706b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.GROUP_ALERT_ALL;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
@@ -38,6 +39,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
@@ -50,9 +52,11 @@
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Color;
+import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -1596,6 +1600,77 @@
         assertTrue(interrupter.isInterruptive());
     }
 
+    @Test
+    public void testBubbleSuppressedNotificationDoesntMakeSound() {
+        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+                        mock(PendingIntent.class), mock(Icon.class))
+                .build();
+
+        NotificationRecord record = getBuzzyNotification();
+        metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+        record.getNotification().setBubbleMetadata(metadata);
+        record.setAllowBubble(true);
+        record.getNotification().flags |= FLAG_BUBBLE;
+        record.isUpdate = true;
+        record.setInterruptive(false);
+
+        mService.buzzBeepBlinkLocked(record);
+        verifyNeverVibrate();
+    }
+
+    @Test
+    public void testOverflowBubbleSuppressedNotificationDoesntMakeSound() {
+        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+                mock(PendingIntent.class), mock(Icon.class))
+                .build();
+
+        NotificationRecord record = getBuzzyNotification();
+        metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+        record.getNotification().setBubbleMetadata(metadata);
+        record.setFlagBubbleRemoved(true);
+        record.setAllowBubble(true);
+        record.isUpdate = true;
+        record.setInterruptive(false);
+
+        mService.buzzBeepBlinkLocked(record);
+        verifyNeverVibrate();
+    }
+
+    @Test
+    public void testBubbleUpdateMakesSound() {
+        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+                mock(PendingIntent.class), mock(Icon.class))
+                .build();
+
+        NotificationRecord record = getBuzzyNotification();
+        record.getNotification().setBubbleMetadata(metadata);
+        record.setAllowBubble(true);
+        record.getNotification().flags |= FLAG_BUBBLE;
+        record.isUpdate = true;
+        record.setInterruptive(true);
+
+        mService.buzzBeepBlinkLocked(record);
+        verifyVibrate(1);
+    }
+
+    @Test
+    public void testNewBubbleSuppressedNotifMakesSound() {
+        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
+                mock(PendingIntent.class), mock(Icon.class))
+                .build();
+
+        NotificationRecord record = getBuzzyNotification();
+        metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+        record.getNotification().setBubbleMetadata(metadata);
+        record.setAllowBubble(true);
+        record.getNotification().flags |= FLAG_BUBBLE;
+        record.isUpdate = false;
+        record.setInterruptive(true);
+
+        mService.buzzBeepBlinkLocked(record);
+        verifyVibrate(1);
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 338ed06..f1dc098 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.timeout;
 
 import android.app.ActivityOptions;
@@ -53,6 +54,7 @@
 
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
+import java.util.function.ToIntFunction;
 
 /**
  * Tests for the {@link ActivityMetricsLaunchObserver} class.
@@ -158,6 +160,41 @@
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
+    @Test
+    public void testLaunchState() {
+        final ToIntFunction<Boolean> launchTemplate = doRelaunch -> {
+            clearInvocations(mLaunchObserver);
+            onActivityLaunched(mTopActivity);
+            notifyTransitionStarting(mTopActivity);
+            if (doRelaunch) {
+                mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity);
+            }
+            final ActivityMetricsLogger.TransitionInfoSnapshot info =
+                    notifyWindowsDrawn(mTopActivity);
+            verifyOnActivityLaunchFinished(mTopActivity);
+            return info.getLaunchState();
+        };
+
+        final WindowProcessController app = mTopActivity.app;
+        // Assume that the process is started (ActivityBuilder has mocked the returned value of
+        // ATMS#getProcessController) but the activity has not attached process.
+        mTopActivity.app = null;
+        assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+                .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
+
+        mTopActivity.app = app;
+        assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+                .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
+
+        assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */))
+                .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
+
+        mTopActivity.app = null;
+        doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
+        assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+                .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
+    }
+
     private void onActivityLaunched(ActivityRecord activity) {
         onIntentStarted(activity.intent);
         notifyActivityLaunched(START_SUCCESS, activity);
@@ -168,15 +205,10 @@
 
     @Test
     public void testOnActivityLaunchFinished() {
-        // Assume that the process is started (ActivityBuilder has mocked the returned value of
-        // ATMS#getProcessController) but the activity has not attached process.
-        mTopActivity.app = null;
         onActivityLaunched(mTopActivity);
 
         notifyTransitionStarting(mTopActivity);
-        final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
-        assertWithMessage("Warm launch").that(info.getLaunchState())
-                .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
+        notifyWindowsDrawn(mTopActivity);
 
         verifyOnActivityLaunchFinished(mTopActivity);
         verifyNoMoreInteractions(mLaunchObserver);
@@ -231,8 +263,6 @@
         assertWithMessage("Record start source").that(info.sourceType)
                 .isEqualTo(SourceInfo.TYPE_LAUNCHER);
         assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10);
-        assertWithMessage("Hot launch").that(info.getLaunchState())
-                .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
 
         verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
         verifyOnActivityLaunchFinished(mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 35ce923..53ade0e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,10 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -125,61 +123,65 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityRecordTests extends WindowTestsBase {
-    private Task mStack;
-    private Task mTask;
-    private ActivityRecord mActivity;
 
     @Before
     public void setUp() throws Exception {
-        mTask = new TaskBuilder(mSupervisor)
-                .setCreateParentTask(true).setCreateActivity(true).build();
-        mStack = mTask.getRootTask();
-        mActivity = mTask.getTopNonFinishingActivity();
-
         setBooted(mAtm);
     }
 
     @Test
     public void testStackCleanupOnClearingTask() {
-        mActivity.onParentChanged(null /*newParent*/, mActivity.getTask());
-        verify(mStack, times(1)).cleanUpActivityReferences(any());
+        final ActivityRecord activity = createActivityWith2LevelTask();
+        final Task task = activity.getTask();
+        final Task rootTask = activity.getRootTask();
+        activity.onParentChanged(null /*newParent*/, task);
+        verify(rootTask, times(1)).cleanUpActivityReferences(any());
     }
 
     @Test
     public void testStackCleanupOnActivityRemoval() {
-        mTask.removeChild(mActivity);
-        verify(mStack, times(1)).cleanUpActivityReferences(any());
+        final ActivityRecord activity = createActivityWith2LevelTask();
+        final Task task = activity.getTask();
+        final Task rootTask = activity.getRootTask();
+        task.removeChild(activity);
+        verify(rootTask, times(1)).cleanUpActivityReferences(any());
     }
 
     @Test
     public void testStackCleanupOnTaskRemoval() {
-        mStack.removeChild(mTask, null /*reason*/);
-        // Stack should be gone on task removal.
-        assertNull(mAtm.mRootWindowContainer.getStack(mStack.mTaskId));
+        final ActivityRecord activity = createActivityWith2LevelTask();
+        final Task task = activity.getTask();
+        final Task rootTask = activity.getRootTask();
+        rootTask.removeChild(task, null /*reason*/);
+        // parentTask should be gone on task removal.
+        assertNull(mAtm.mRootWindowContainer.getStack(rootTask.mTaskId));
     }
 
     @Test
     public void testRemoveChildWithOverlayActivity() {
-        final ActivityRecord overlayActivity =
-                new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(task).build();
         overlayActivity.setTaskOverlay(true);
-        final ActivityRecord overlayActivity2 =
-                new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord overlayActivity2 = new ActivityBuilder(mAtm).setTask(task).build();
         overlayActivity2.setTaskOverlay(true);
 
-        mTask.removeChild(overlayActivity2, "test");
+        task.removeChild(overlayActivity2, "test");
         verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any());
     }
 
     @Test
     public void testNoCleanupMovingActivityInSameStack() {
-        final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
-        mActivity.reparent(newTask, 0, null /*reason*/);
-        verify(mStack, times(0)).cleanUpActivityReferences(any());
+        final ActivityRecord activity = createActivityWith2LevelTask();
+        final Task rootTask = activity.getRootTask();
+        final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask).build();
+        activity.reparent(newTask, 0, null /*reason*/);
+        verify(rootTask, times(0)).cleanUpActivityReferences(any());
     }
 
     @Test
     public void testPausingWhenVisibleFromStopped() throws Exception {
+        final ActivityRecord activity = createActivityWithTask();
         final MutableBoolean pauseFound = new MutableBoolean(false);
         doAnswer((InvocationOnMock invocationOnMock) -> {
             final ClientTransaction transaction = invocationOnMock.getArgument(0);
@@ -187,49 +189,50 @@
                 pauseFound.value = true;
             }
             return null;
-        }).when(mActivity.app.getThread()).scheduleTransaction(any());
+        }).when(activity.app.getThread()).scheduleTransaction(any());
 
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+        activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
         // The activity is in the focused stack so it should be resumed.
-        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(RESUMED));
+        activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(activity.isState(RESUMED));
         assertFalse(pauseFound.value);
 
         // Make the activity non focusable
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
-        doReturn(false).when(mActivity).isFocusable();
+        activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+        doReturn(false).when(activity).isFocusable();
 
         // If the activity is not focusable, it should move to paused.
-        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(PAUSING));
+        activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(activity.isState(PAUSING));
         assertTrue(pauseFound.value);
 
         // Make sure that the state does not change for current non-stopping states.
-        mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
-        doReturn(true).when(mActivity).isFocusable();
+        activity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+        doReturn(true).when(activity).isFocusable();
 
-        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
 
-        assertTrue(mActivity.isState(INITIALIZING));
+        assertTrue(activity.isState(INITIALIZING));
 
         // Make sure the state does not change if we are not the current top activity.
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
+        activity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
 
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        mStack.mTranslucentActivityWaiting = topActivity;
-        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(STARTED));
+        final Task task = activity.getTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        task.mTranslucentActivityWaiting = topActivity;
+        activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(activity.isState(STARTED));
 
-        mStack.mTranslucentActivityWaiting = null;
+        task.mTranslucentActivityWaiting = null;
         topActivity.setOccludesParent(false);
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque");
-        mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(STARTED));
+        activity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque");
+        activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        assertTrue(activity.isState(STARTED));
     }
 
-    private void ensureActivityConfiguration() {
-        mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+    private void ensureActivityConfiguration(ActivityRecord activity) {
+        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
     }
 
     @Test
@@ -245,136 +248,145 @@
 
     @Test
     public void testsApplyOptionsLocked() {
+        final ActivityRecord activity = createActivityWithTask();
         ActivityOptions activityOptions = ActivityOptions.makeBasic();
 
         // Set and apply options for ActivityRecord. Pending options should be cleared
-        mActivity.updateOptionsLocked(activityOptions);
-        mActivity.applyOptionsLocked();
-        assertNull(mActivity.pendingOptions);
+        activity.updateOptionsLocked(activityOptions);
+        activity.applyOptionsLocked();
+        assertNull(activity.pendingOptions);
 
         // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options.
         // Pending options should be cleared for both ActivityRecords
-        ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(mTask).build();
+        ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
         activity2.updateOptionsLocked(activityOptions);
-        mActivity.updateOptionsLocked(activityOptions);
-        mActivity.applyOptionsLocked();
-        assertNull(mActivity.pendingOptions);
+        activity.updateOptionsLocked(activityOptions);
+        activity.applyOptionsLocked();
+        assertNull(activity.pendingOptions);
         assertNull(activity2.pendingOptions);
 
         // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options.
         // Pending options should be cleared for only ActivityRecord that was applied
-        Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
-        activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
+        activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity2.updateOptionsLocked(activityOptions);
-        mActivity.updateOptionsLocked(activityOptions);
-        mActivity.applyOptionsLocked();
-        assertNull(mActivity.pendingOptions);
+        activity.updateOptionsLocked(activityOptions);
+        activity.applyOptionsLocked();
+        assertNull(activity.pendingOptions);
         assertNotNull(activity2.pendingOptions);
     }
 
     @Test
     public void testNewOverrideConfigurationIncrementsSeq() {
+        final ActivityRecord activity = createActivityWithTask();
         final Configuration newConfig = new Configuration();
 
-        final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
-        mActivity.onRequestedOverrideConfigurationChanged(newConfig);
-        assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+        final int prevSeq = activity.getMergedOverrideConfiguration().seq;
+        activity.onRequestedOverrideConfigurationChanged(newConfig);
+        assertEquals(prevSeq + 1, activity.getMergedOverrideConfiguration().seq);
     }
 
     @Test
     public void testNewParentConfigurationIncrementsSeq() {
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
         final Configuration newConfig = new Configuration(
-                mTask.getRequestedOverrideConfiguration());
+                task.getRequestedOverrideConfiguration());
         newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
                 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
 
-        final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
-        mTask.onRequestedOverrideConfigurationChanged(newConfig);
-        assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+        final int prevSeq = activity.getMergedOverrideConfiguration().seq;
+        task.onRequestedOverrideConfigurationChanged(newConfig);
+        assertEquals(prevSeq + 1, activity.getMergedOverrideConfiguration().seq);
     }
 
     @Test
     public void testSetsRelaunchReason_NotDragResizing() {
-        mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.RESUMED, "Testing");
 
-        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
-        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                mActivity.getConfiguration()));
+        task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                activity.getConfiguration()));
 
-        mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
-        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        activity.info.configChanges &= ~CONFIG_ORIENTATION;
+        final Configuration newConfig = new Configuration(task.getConfiguration());
         newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
                 ? ORIENTATION_LANDSCAPE
                 : ORIENTATION_PORTRAIT;
-        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+        task.onRequestedOverrideConfigurationChanged(newConfig);
 
-        mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+        activity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 
-        ensureActivityConfiguration();
+        ensureActivityConfiguration(activity);
 
         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE,
-                mActivity.mRelaunchReason);
+                activity.mRelaunchReason);
     }
 
     @Test
     public void testSetsRelaunchReason_DragResizing() {
-        mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.RESUMED, "Testing");
 
-        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
-        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                mActivity.getConfiguration()));
+        task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                activity.getConfiguration()));
 
-        mActivity.info.configChanges &= ~CONFIG_ORIENTATION;
-        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        activity.info.configChanges &= ~CONFIG_ORIENTATION;
+        final Configuration newConfig = new Configuration(task.getConfiguration());
         newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
                 ? ORIENTATION_LANDSCAPE
                 : ORIENTATION_PORTRAIT;
-        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+        task.onRequestedOverrideConfigurationChanged(newConfig);
 
-        doReturn(true).when(mTask).isDragResizing();
+        doReturn(true).when(task).isDragResizing();
 
-        mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+        activity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 
-        ensureActivityConfiguration();
+        ensureActivityConfiguration(activity);
 
         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE,
-                mActivity.mRelaunchReason);
+                activity.mRelaunchReason);
     }
 
     @Test
     public void testSetsRelaunchReason_NonResizeConfigChanges() {
-        mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.RESUMED, "Testing");
 
-        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
-        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                mActivity.getConfiguration()));
+        task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
+        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                activity.getConfiguration()));
 
-        mActivity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
-        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        activity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
+        final Configuration newConfig = new Configuration(task.getConfiguration());
         newConfig.fontScale = 5;
-        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+        task.onRequestedOverrideConfigurationChanged(newConfig);
 
-        mActivity.mRelaunchReason =
+        activity.mRelaunchReason =
                 ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 
-        ensureActivityConfiguration();
+        ensureActivityConfiguration(activity);
 
         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE,
-                mActivity.mRelaunchReason);
+                activity.mRelaunchReason);
     }
 
     @Test
     public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
-        mActivity = new ActivityBuilder(mAtm)
-                .setTask(mTask)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setCreateTask(true)
                 .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
-        mActivity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(Task.ActivityState.RESUMED, "Testing");
 
-        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                mActivity.getConfiguration()));
+        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                activity.getConfiguration()));
 
-        final Configuration newConfig = new Configuration(mActivity.getConfiguration());
+        final Configuration newConfig = new Configuration(activity.getConfiguration());
         final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
         final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
         if (newConfig.orientation == ORIENTATION_PORTRAIT) {
@@ -388,7 +400,7 @@
         }
 
         // Mimic the behavior that display doesn't handle app's requested orientation.
-        final DisplayContent dc = mTask.getDisplayContent();
+        final DisplayContent dc = activity.getTask().getDisplayContent();
         doReturn(false).when(dc).onDescendantOrientationChanged(any(), any());
         doReturn(false).when(dc).handlesOrientationChangeFromDescendant();
 
@@ -404,24 +416,26 @@
                 throw new IllegalStateException("Orientation in new config should be either"
                         + "landscape or portrait.");
         }
-        mActivity.setRequestedOrientation(requestedOrientation);
+        activity.setRequestedOrientation(requestedOrientation);
 
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(newConfig);
-        verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(mActivity.app.getThread()),
-                eq(mActivity.appToken), eq(expected));
+        verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
+                eq(activity.appToken), eq(expected));
     }
 
     @Test
     public void ignoreRequestedOrientationInFreeformWindows() {
-        mStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
         final Rect stableRect = new Rect();
-        mStack.mDisplayContent.getStableRect(stableRect);
+        task.mDisplayContent.getStableRect(stableRect);
 
         // Carve out non-decor insets from stableRect
         final Rect insets = new Rect();
-        final DisplayInfo displayInfo = mStack.mDisplayContent.getDisplayInfo();
-        final DisplayPolicy policy = mStack.mDisplayContent.getDisplayPolicy();
+        final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
+        final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
         policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
                 displayInfo.logicalHeight, displayInfo.displayCutout, insets);
         policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
@@ -440,27 +454,30 @@
             bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
             bounds.right = bounds.left + newWidth;
         }
-        mTask.setBounds(bounds);
+        task.setBounds(bounds);
 
         // Requests orientation that's different from its bounds.
-        mActivity.setRequestedOrientation(
+        activity.setRequestedOrientation(
                 isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
 
         // Asserts it has orientation derived from bounds.
         assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
-                mActivity.getConfiguration().orientation);
+                activity.getConfiguration().orientation);
     }
 
     @Test
     public void ignoreRequestedOrientationInSplitWindows() {
-        mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ActivityRecord activity = createActivityWith2LevelTask();
+        final Task task = activity.getTask();
+        final Task rootTask = activity.getRootTask();
+        rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final Rect stableRect = new Rect();
-        mStack.mDisplayContent.getStableRect(stableRect);
+        rootTask.mDisplayContent.getStableRect(stableRect);
 
         // Carve out non-decor insets from stableRect
         final Rect insets = new Rect();
-        final DisplayInfo displayInfo = mStack.mDisplayContent.getDisplayInfo();
-        final DisplayPolicy policy = mStack.mDisplayContent.getDisplayPolicy();
+        final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
+        final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
         policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
                 displayInfo.logicalHeight, displayInfo.displayCutout, insets);
         policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
@@ -479,85 +496,91 @@
             bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
             bounds.right = bounds.left + newWidth;
         }
-        mTask.setBounds(bounds);
+        task.setBounds(bounds);
 
         // Requests orientation that's different from its bounds.
-        mActivity.setRequestedOrientation(
+        activity.setRequestedOrientation(
                 isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
 
         // Asserts it has orientation derived from bounds.
         assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
-                mActivity.getConfiguration().orientation);
+                activity.getConfiguration().orientation);
     }
 
     @Test
     public void testShouldMakeActive_deferredResume() {
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
 
         mSupervisor.beginDeferResume();
-        assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
+        assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
 
         mSupervisor.endDeferResume();
-        assertEquals(true, mActivity.shouldMakeActive(null /* activeActivity */));
+        assertEquals(true, activity.shouldMakeActive(null /* activeActivity */));
     }
 
     @Test
     public void testShouldMakeActive_nonTopVisible() {
-        ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
         finishingActivity.finishing = true;
-        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
 
-        assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
+        assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
     }
 
     @Test
     public void testShouldResume_stackVisibility() {
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
-        spyOn(mStack);
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
 
-        doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
-        assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
+        doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+        assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
-        assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+        doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(task).getVisibility(null);
+        assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(TASK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
-        assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+        doReturn(TASK_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
+        assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
     }
 
     @Test
     public void testShouldResumeOrPauseWithResults() {
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
-        spyOn(mStack);
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
 
-        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        mActivity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        activity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
         topActivity.finishing = true;
 
-        doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
-        assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
-        assertEquals(false, mActivity.shouldPauseActivity(null /*activeActivity */));
+        doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+        assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
+        assertEquals(false, activity.shouldPauseActivity(null /*activeActivity */));
     }
 
     @Test
     public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
-        mActivity = new ActivityBuilder(mAtm)
-                .setTask(mTask)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setCreateTask(true)
                 .setLaunchTaskBehind(true)
                 .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
+        final Task task = activity.getTask();
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
 
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         try {
             doReturn(false).when(stack).isTranslucent(any());
-            assertTrue(mStack.shouldBeVisible(null /* starting */));
+            assertTrue(task.shouldBeVisible(null /* starting */));
 
-            mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                    mActivity.getConfiguration()));
+            activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                    activity.getConfiguration()));
 
-            final Configuration newConfig = new Configuration(mActivity.getConfiguration());
+            final Configuration newConfig = new Configuration(activity.getConfiguration());
             final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
             final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
             if (newConfig.orientation == ORIENTATION_PORTRAIT) {
@@ -570,15 +593,15 @@
                 newConfig.screenHeightDp = longSide;
             }
 
-            mTask.onConfigurationChanged(newConfig);
+            task.onConfigurationChanged(newConfig);
 
-            mActivity.ensureActivityConfiguration(0 /* globalChanges */,
+            activity.ensureActivityConfiguration(0 /* globalChanges */,
                     false /* preserveWindow */, true /* ignoreStopState */);
 
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(newConfig);
             verify(mAtm.getLifecycleManager()).scheduleTransaction(
-                    eq(mActivity.app.getThread()), eq(mActivity.appToken), eq(expected));
+                    eq(activity.app.getThread()), eq(activity.appToken), eq(expected));
         } finally {
             stack.getDisplayArea().removeChild(stack);
         }
@@ -586,16 +609,18 @@
 
     @Test
     public void testShouldStartWhenMakeClientActive() {
-        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
         topActivity.setOccludesParent(false);
-        mActivity.setState(Task.ActivityState.STOPPED, "Testing");
-        mActivity.setVisibility(true);
-        mActivity.makeActiveIfNeeded(null /* activeActivity */);
-        assertEquals(STARTED, mActivity.getState());
+        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setVisibility(true);
+        activity.makeActiveIfNeeded(null /* activeActivity */);
+        assertEquals(STARTED, activity.getState());
     }
 
     @Test
     public void testTakeOptions() {
+        final ActivityRecord activity = createActivityWithTask();
         ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
                 new RemoteAnimationAdapter(new Stub() {
 
@@ -611,13 +636,13 @@
 
                     }
                 }, 0, 0));
-        mActivity.updateOptionsLocked(opts);
-        assertNotNull(mActivity.takeOptionsLocked(true /* fromClient */));
-        assertNotNull(mActivity.pendingOptions);
+        activity.updateOptionsLocked(opts);
+        assertNotNull(activity.takeOptionsLocked(true /* fromClient */));
+        assertNotNull(activity.pendingOptions);
 
-        mActivity.updateOptionsLocked(ActivityOptions.makeBasic());
-        assertNotNull(mActivity.takeOptionsLocked(false /* fromClient */));
-        assertNull(mActivity.pendingOptions);
+        activity.updateOptionsLocked(ActivityOptions.makeBasic());
+        assertNotNull(activity.takeOptionsLocked(false /* fromClient */));
+        assertNull(activity.pendingOptions);
     }
 
     @Test
@@ -626,7 +651,7 @@
                 Resources.getSystem().getString(R.string.config_chooserActivity));
         ActivityRecord chooserActivity = new ActivityBuilder(mAtm).setComponent(
                 chooserComponent).build();
-        assertThat(mActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
+        assertThat(chooserActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
     }
 
     /**
@@ -635,49 +660,52 @@
      */
     @Test
     public void testHasSavedState() {
-        assertTrue(mActivity.hasSavedState());
+        final ActivityRecord activity = createActivityWithTask();
+        assertTrue(activity.hasSavedState());
 
-        ActivityRecord.activityResumedLocked(mActivity.appToken);
-        assertFalse(mActivity.hasSavedState());
-        assertNull(mActivity.getSavedState());
+        ActivityRecord.activityResumedLocked(activity.appToken);
+        assertFalse(activity.hasSavedState());
+        assertNull(activity.getSavedState());
     }
 
     /** Verify the behavior of {@link ActivityRecord#setSavedState(Bundle)}. */
     @Test
     public void testUpdateSavedState() {
-        mActivity.setSavedState(null /* savedState */);
-        assertFalse(mActivity.hasSavedState());
-        assertNull(mActivity.getSavedState());
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setSavedState(null /* savedState */);
+        assertFalse(activity.hasSavedState());
+        assertNull(activity.getSavedState());
 
         final Bundle savedState = new Bundle();
         savedState.putString("test", "string");
-        mActivity.setSavedState(savedState);
-        assertTrue(mActivity.hasSavedState());
-        assertEquals(savedState, mActivity.getSavedState());
+        activity.setSavedState(savedState);
+        assertTrue(activity.hasSavedState());
+        assertEquals(savedState, activity.getSavedState());
     }
 
     /** Verify the correct updates of saved state when activity client reports stop. */
     @Test
     public void testUpdateSavedState_activityStopped() {
+        final ActivityRecord activity = createActivityWithTask();
         final Bundle savedState = new Bundle();
         savedState.putString("test", "string");
         final PersistableBundle persistentSavedState = new PersistableBundle();
         persistentSavedState.putString("persist", "string");
 
         // Set state to STOPPING, or ActivityRecord#activityStoppedLocked() call will be ignored.
-        mActivity.setState(STOPPING, "test");
-        mActivity.activityStopped(savedState, persistentSavedState, "desc");
-        assertTrue(mActivity.hasSavedState());
-        assertEquals(savedState, mActivity.getSavedState());
-        assertEquals(persistentSavedState, mActivity.getPersistentSavedState());
+        activity.setState(STOPPING, "test");
+        activity.activityStopped(savedState, persistentSavedState, "desc");
+        assertTrue(activity.hasSavedState());
+        assertEquals(savedState, activity.getSavedState());
+        assertEquals(persistentSavedState, activity.getPersistentSavedState());
 
         // Sending 'null' for saved state can only happen due to timeout, so previously stored saved
         // states should not be overridden.
-        mActivity.setState(STOPPING, "test");
-        mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc");
-        assertTrue(mActivity.hasSavedState());
-        assertEquals(savedState, mActivity.getSavedState());
-        assertEquals(persistentSavedState, mActivity.getPersistentSavedState());
+        activity.setState(STOPPING, "test");
+        activity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc");
+        assertTrue(activity.hasSavedState());
+        assertEquals(savedState, activity.getSavedState());
+        assertEquals(persistentSavedState, activity.getPersistentSavedState());
     }
 
     /**
@@ -686,19 +714,20 @@
      */
     @Test
     public void testFinishActivityIfPossible_cancelled() {
+        final ActivityRecord activity = createActivityWithTask();
         // Mark activity as finishing
-        mActivity.finishing = true;
+        activity.finishing = true;
         assertEquals("Duplicate finish request must be ignored", FINISH_RESULT_CANCELLED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertTrue(mActivity.finishing);
-        assertTrue(mActivity.isInStackLocked());
+                activity.finishIfPossible("test", false /* oomAdj */));
+        assertTrue(activity.finishing);
+        assertTrue(activity.isInStackLocked());
 
         // Remove activity from task
-        mActivity.finishing = false;
-        mActivity.onParentChanged(null /*newParent*/, mActivity.getTask());
+        activity.finishing = false;
+        activity.onParentChanged(null /*newParent*/, activity.getTask());
         assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_CANCELLED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertFalse(mActivity.finishing);
+                activity.finishIfPossible("test", false /* oomAdj */));
+        assertFalse(activity.finishing);
     }
 
     /**
@@ -707,20 +736,21 @@
      */
     @Test
     public void testFinishActivityIfPossible_requested() {
-        mActivity.finishing = false;
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = false;
         assertEquals("Currently resumed activity must be prepared removal", FINISH_RESULT_REQUESTED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertTrue(mActivity.finishing);
-        assertTrue(mActivity.isInStackLocked());
+                activity.finishIfPossible("test", false /* oomAdj */));
+        assertTrue(activity.finishing);
+        assertTrue(activity.isInStackLocked());
 
         // First request to finish activity must schedule a "destroy" request to the client.
         // Activity must be removed from history after the client reports back or after timeout.
-        mActivity.finishing = false;
-        mActivity.setState(STOPPED, "test");
+        activity.finishing = false;
+        activity.setState(STOPPED, "test");
         assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REQUESTED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertTrue(mActivity.finishing);
-        assertTrue(mActivity.isInStackLocked());
+                activity.finishIfPossible("test", false /* oomAdj */));
+        assertTrue(activity.finishing);
+        assertTrue(activity.isInStackLocked());
     }
 
     /**
@@ -728,26 +758,28 @@
      */
     @Test
     public void testFinishActivityIfPossible_removed() {
+        final ActivityRecord activity = createActivityWithTask();
         // Prepare the activity record to be ready for immediate removal. It should be invisible and
         // have no process. Otherwise, request to finish it will send a message to client first.
-        mActivity.setState(STOPPED, "test");
-        mActivity.mVisibleRequested = false;
-        mActivity.nowVisible = false;
+        activity.setState(STOPPED, "test");
+        activity.mVisibleRequested = false;
+        activity.nowVisible = false;
         // Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
         // this will cause NPE when updating task's process.
-        mActivity.app = null;
+        activity.app = null;
 
         // Put a visible activity on top, so the finishing activity doesn't have to wait until the
         // next activity reports idle to destroy it.
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "test");
 
         assertEquals("Activity outside of task/stack cannot be finished", FINISH_RESULT_REMOVED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertTrue(mActivity.finishing);
-        assertFalse(mActivity.isInStackLocked());
+                activity.finishIfPossible("test", false /* oomAdj */));
+        assertTrue(activity.finishing);
+        assertFalse(activity.isInStackLocked());
     }
 
     /**
@@ -756,24 +788,26 @@
      */
     @Test
     public void testFinishActivityIfPossible_adjustStackOrder() {
-        // Prepare the stacks with order (top to bottom): mStack, stack1, stack2.
-        final Task stack1 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
-        mStack.moveToFront("test");
-        // The stack2 is needed here for moving back to simulate the
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        // Prepare the tasks with order (top to bottom): task, task1, task2.
+        final Task task1 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        task.moveToFront("test");
+        // The task2 is needed here for moving back to simulate the
         // {@link DisplayContent#mPreferredTopFocusableStack} is cleared, so
         // {@link DisplayContent#getFocusedStack} will rely on the order of focusable-and-visible
-        // stacks. Then when mActivity is finishing, its stack will be invisible (no running
-        // activities in the stack) that is the key condition to verify.
-        final Task stack2 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
-        stack2.moveToBack("test", stack2.getBottomMostTask());
+        // tasks. Then when mActivity is finishing, its task will be invisible (no running
+        // activities in the task) that is the key condition to verify.
+        final Task task2 = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        task2.moveToBack("test", task2.getBottomMostTask());
 
-        assertTrue(mStack.isTopStackInDisplayArea());
+        assertTrue(task.isTopStackInDisplayArea());
 
-        mActivity.setState(RESUMED, "test");
-        mActivity.finishIfPossible(0 /* resultCode */, null /* resultData */,
+        activity.setState(RESUMED, "test");
+        activity.finishIfPossible(0 /* resultCode */, null /* resultData */,
                 null /* resultGrants */, "test", false /* oomAdj */);
 
-        assertTrue(stack1.isTopStackInDisplayArea());
+        assertTrue(task1.isTopStackInDisplayArea());
     }
 
     /**
@@ -783,22 +817,25 @@
     @Test
     public void testFinishActivityIfPossible_adjustStackOrderOrganizedRoot() {
         // Make mStack be a the root task that created by task organizer
-        mStack.mCreatedByOrganizer = true;
+        final Task rootableTask = new TaskBuilder(mSupervisor)
+                .setCreateParentTask(true).setCreateActivity(true).build();
+        final Task rootTask = rootableTask.getRootTask();
+        rootTask.mCreatedByOrganizer = true;
 
-        // Have two tasks (topRootableTask and mTask) as the children of mStack.
-        ActivityRecord topActivity = new ActivityBuilder(mActivity.mAtmService)
+        // Have two tasks (topRootableTask and rootableTask) as the children of rootTask.
+        ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
-                .setParentTask(mStack)
+                .setParentTask(rootTask)
                 .build();
         Task topRootableTask = topActivity.getTask();
         topRootableTask.moveToFront("test");
-        assertTrue(mStack.isTopStackInDisplayArea());
+        assertTrue(rootTask.isTopStackInDisplayArea());
 
         // Finish top activity and verify the next focusable rootable task has adjusted to top.
         topActivity.setState(RESUMED, "test");
         topActivity.finishIfPossible(0 /* resultCode */, null /* resultData */,
                 null /* resultGrants */, "test", false /* oomAdj */);
-        assertEquals(mTask, mStack.getTopMostTask());
+        assertEquals(rootableTask, rootTask.getTopMostTask());
     }
 
     /**
@@ -808,6 +845,8 @@
      */
     @Test
     public void testFinishActivityIfPossible_PreferredTopStackChanged() {
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
         final ActivityRecord topActivityOnNonTopDisplay =
                 createActivityOnDisplay(true /* defaultDisplay */, null /* process */);
         Task topRootableTask = topActivityOnNonTopDisplay.getRootTask();
@@ -830,8 +869,8 @@
         topActivityOnNonTopDisplay.setState(RESUMED, "test");
         topActivityOnNonTopDisplay.finishIfPossible(0 /* resultCode */, null /* resultData */,
                 null /* resultGrants */, "test", false /* oomAdj */);
-        assertEquals(mTask, mStack.getTopMostTask());
-        assertEquals(mStack, mActivity.getDisplayArea().mPreferredTopFocusableStack);
+        assertEquals(task, task.getTopMostTask());
+        assertEquals(task, activity.getDisplayArea().mPreferredTopFocusableStack);
     }
 
     /**
@@ -839,13 +878,14 @@
      */
     @Test
     public void testFinishActivityIfPossible_resumedStartsPausing() {
-        mActivity.finishing = false;
-        mActivity.setState(RESUMED, "test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = false;
+        activity.setState(RESUMED, "test");
         assertEquals("Currently resumed activity must be paused before removal",
-                FINISH_RESULT_REQUESTED, mActivity.finishIfPossible("test", false /* oomAdj */));
-        assertEquals(PAUSING, mActivity.getState());
-        verify(mActivity).setVisibility(eq(false));
-        verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+                FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */));
+        assertEquals(PAUSING, activity.getState());
+        verify(activity).setVisibility(eq(false));
+        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
     }
 
     /**
@@ -853,14 +893,15 @@
      */
     @Test
     public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() {
+        final ActivityRecord activity = createActivityWithTask();
         final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
         for (ActivityState state : states) {
-            mActivity.finishing = false;
-            mActivity.setState(state, "test");
-            reset(mActivity);
+            activity.finishing = false;
+            activity.setState(state, "test");
+            reset(activity);
             assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
-                    mActivity.finishIfPossible("test", false /* oomAdj */));
-            verify(mActivity).completeFinishing(anyString());
+                    activity.finishIfPossible("test", false /* oomAdj */));
+            verify(activity).completeFinishing(anyString());
         }
     }
 
@@ -869,11 +910,12 @@
      */
     @Test
     public void testFinishActivityIfPossible_pausing() {
-        mActivity.finishing = false;
-        mActivity.setState(PAUSING, "test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = false;
+        activity.setState(PAUSING, "test");
         assertEquals("Finish must be requested", FINISH_RESULT_REQUESTED,
-                mActivity.finishIfPossible("test", false /* oomAdj */));
-        verify(mActivity, never()).completeFinishing(anyString());
+                activity.finishIfPossible("test", false /* oomAdj */));
+        verify(activity, never()).completeFinishing(anyString());
     }
 
     /**
@@ -882,14 +924,16 @@
      */
     @Test
     public void testFinishActivityIfPossible_visibleResumedPreparesAppTransition() {
-        mActivity.finishing = false;
-        mActivity.mVisibleRequested = true;
-        mActivity.setState(RESUMED, "test");
-        mActivity.finishIfPossible("test", false /* oomAdj */);
+        final ActivityRecord activity = createActivityWithTask();
+        clearInvocations(activity.mDisplayContent);
+        activity.finishing = false;
+        activity.mVisibleRequested = true;
+        activity.setState(RESUMED, "test");
+        activity.finishIfPossible("test", false /* oomAdj */);
 
-        verify(mActivity).setVisibility(eq(false));
-        verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
-        verify(mActivity.mDisplayContent, never()).executeAppTransition();
+        verify(activity).setVisibility(eq(false));
+        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+        verify(activity.mDisplayContent, never()).executeAppTransition();
     }
 
     /**
@@ -897,14 +941,16 @@
      */
     @Test
     public void testFinishActivityIfPossible_visibleNotResumedExecutesAppTransition() {
-        mActivity.finishing = false;
-        mActivity.mVisibleRequested = true;
-        mActivity.setState(PAUSED, "test");
-        mActivity.finishIfPossible("test", false /* oomAdj */);
+        final ActivityRecord activity = createActivityWithTask();
+        clearInvocations(activity.mDisplayContent);
+        activity.finishing = false;
+        activity.mVisibleRequested = true;
+        activity.setState(PAUSED, "test");
+        activity.finishIfPossible("test", false /* oomAdj */);
 
-        verify(mActivity, atLeast(1)).setVisibility(eq(false));
-        verify(mActivity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
-        verify(mActivity.mDisplayContent).executeAppTransition();
+        verify(activity, atLeast(1)).setVisibility(eq(false));
+        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
+        verify(activity.mDisplayContent).executeAppTransition();
     }
 
     /**
@@ -912,15 +958,16 @@
      */
     @Test
     public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
+        final ActivityRecord activity = createActivityWithTask();
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
         new ActivityBuilder(mAtm).build();
-        mActivity.mVisibleRequested = false;
-        mActivity.setState(STOPPED, "test");
+        activity.mVisibleRequested = false;
+        activity.setState(STOPPED, "test");
 
-        mActivity.finishIfPossible("test", false /* oomAdj */);
+        activity.finishIfPossible("test", false /* oomAdj */);
 
-        verify(mActivity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
+        verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
     }
 
     /**
@@ -928,8 +975,9 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testCompleteFinishing_failNotFinishing() {
-        mActivity.finishing = false;
-        mActivity.completeFinishing("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = false;
+        activity.completeFinishing("test");
     }
 
     /**
@@ -937,8 +985,9 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testCompleteFinishing_failResumed() {
-        mActivity.setState(RESUMED, "test");
-        mActivity.completeFinishing("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(RESUMED, "test");
+        activity.completeFinishing("test");
     }
 
     /**
@@ -947,13 +996,14 @@
      */
     @Test
     public void testCompleteFinishing_pausing() {
-        mActivity.setState(PAUSING, "test");
-        mActivity.finishing = true;
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(PAUSING, "test");
+        activity.finishing = true;
 
         assertEquals("Activity must not be removed immediately - waiting for paused",
-                mActivity, mActivity.completeFinishing("test"));
-        assertEquals(PAUSING, mActivity.getState());
-        verify(mActivity, never()).destroyIfPossible(anyString());
+                activity, activity.completeFinishing("test"));
+        assertEquals(PAUSING, activity.getState());
+        verify(activity, never()).destroyIfPossible(anyString());
     }
 
     /**
@@ -965,7 +1015,9 @@
      */
     @Test
     public void testCompleteFinishing_keepStateOfNextInvisible() {
-        final ActivityRecord currentTop = mActivity;
+        final ActivityRecord currentTop = createActivityWithTask();
+        final Task task = currentTop.getTask();
+
         currentTop.mVisibleRequested = currentTop.nowVisible = true;
 
         // Simulates that {@code currentTop} starts an existing activity from background (so its
@@ -974,7 +1026,7 @@
         final ActivityRecord nextTop = nextStack.getTopNonFinishingActivity();
         nextTop.setState(STOPPED, "test");
 
-        mStack.mPausingActivity = currentTop;
+        task.mPausingActivity = currentTop;
         currentTop.finishing = true;
         currentTop.setState(PAUSED, "test");
         currentTop.completeFinishing("completePauseLocked");
@@ -991,16 +1043,18 @@
      */
     @Test
     public void testCompleteFinishing_waitForNextVisible() {
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as not visible, so that we will wait for it before removing
         // the top one.
-        mActivity.mVisibleRequested = false;
-        mActivity.nowVisible = false;
-        mActivity.setState(STOPPED, "test");
+        activity.mVisibleRequested = false;
+        activity.nowVisible = false;
+        activity.setState(STOPPED, "test");
 
         assertEquals("Activity must not be removed immediately - waiting for next visible",
                 topActivity, topActivity.completeFinishing("test"));
@@ -1017,23 +1071,24 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_sleeping() {
+        final ActivityRecord activity = createActivityWithTask();
         // Create a top activity on a new task
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord topActivity = createActivityWithTask();
         mDisplayContent.setIsSleeping(true);
-        doReturn(true).when(mActivity).shouldBeVisible();
+        doReturn(true).when(activity).shouldBeVisible();
         topActivity.mVisibleRequested = false;
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
 
         // Mark the activity behind (on a separate task) as not visible
-        mActivity.mVisibleRequested = false;
-        mActivity.nowVisible = false;
-        mActivity.setState(STOPPED, "test");
+        activity.mVisibleRequested = false;
+        activity.nowVisible = false;
+        activity.setState(STOPPED, "test");
 
-        clearInvocations(mActivity);
+        clearInvocations(activity);
         topActivity.completeFinishing("test");
-        verify(mActivity).setState(eq(RESUMED), any());
+        verify(activity).setState(eq(RESUMED), any());
         verify(topActivity).destroyIfPossible(anyString());
     }
 
@@ -1042,16 +1097,18 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() {
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = false;
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as not visible, so that we would wait for it before removing
         // the top one.
-        mActivity.mVisibleRequested = false;
-        mActivity.nowVisible = false;
-        mActivity.setState(STOPPED, "test");
+        activity.mVisibleRequested = false;
+        activity.nowVisible = false;
+        activity.setState(STOPPED, "test");
 
         topActivity.completeFinishing("test");
 
@@ -1064,15 +1121,17 @@
      */
     @Test
     public void testCompleteFinishing_waitForIdle() {
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        mActivity.mVisibleRequested = true;
-        mActivity.nowVisible = true;
-        mActivity.setState(RESUMED, "test");
+        activity.mVisibleRequested = true;
+        activity.nowVisible = true;
+        activity.setState(RESUMED, "test");
 
         topActivity.completeFinishing("test");
 
@@ -1085,15 +1144,17 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_stopped() {
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = false;
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        mActivity.mVisibleRequested = true;
-        mActivity.nowVisible = true;
-        mActivity.setState(RESUMED, "test");
+        activity.mVisibleRequested = true;
+        activity.nowVisible = true;
+        activity.setState(RESUMED, "test");
 
         topActivity.completeFinishing("test");
 
@@ -1106,15 +1167,17 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() {
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(activity.getTask()).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        mActivity.mVisibleRequested = true;
-        mActivity.nowVisible = true;
-        mActivity.setState(RESUMED, "test");
+        activity.mVisibleRequested = true;
+        activity.nowVisible = true;
+        activity.setState(RESUMED, "test");
 
         // Add another stack to become focused and make the activity there visible. This way it
         // simulates finishing in non-focused stack in split-screen.
@@ -1136,10 +1199,12 @@
      */
     @Test
     public void testCompleteFinishing_showWhenLocked() {
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
         // Make keyguard locked and set the top activity show-when-locked.
-        KeyguardController keyguardController = mActivity.mTaskSupervisor.getKeyguardController();
+        KeyguardController keyguardController = activity.mTaskSupervisor.getKeyguardController();
         doReturn(true).when(keyguardController).isKeyguardLocked();
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
@@ -1150,7 +1215,7 @@
         topActivity.setShowWhenLocked(true);
 
         // Verify the stack-top activity is occluded keyguard.
-        assertEquals(topActivity, mStack.topRunningActivity());
+        assertEquals(topActivity, task.topRunningActivity());
         assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
 
         // Finish the top activity
@@ -1159,7 +1224,7 @@
         topActivity.completeFinishing("test");
 
         // Verify new top activity does not occlude keyguard.
-        assertEquals(mActivity, mStack.topRunningActivity());
+        assertEquals(activity, task.topRunningActivity());
         assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
     }
 
@@ -1169,18 +1234,19 @@
      */
     @Test
     public void testCompleteFinishing_ensureActivitiesVisible() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
         firstActivity.mVisibleRequested = false;
         firstActivity.nowVisible = false;
         firstActivity.setState(STOPPED, "true");
 
-        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
         secondActivity.mVisibleRequested = true;
         secondActivity.nowVisible = true;
         secondActivity.setState(PAUSED, "true");
 
-        final ActivityRecord translucentActivity =
-                new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
         translucentActivity.mVisibleRequested = true;
         translucentActivity.nowVisible = true;
         translucentActivity.setState(RESUMED, "true");
@@ -1208,13 +1274,13 @@
      */
     @Test
     public void testDestroyIfPossible() {
+        final ActivityRecord activity = createActivityWithTask();
         doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
-        spyOn(mStack);
-        mActivity.destroyIfPossible("test");
+        activity.destroyIfPossible("test");
 
-        assertEquals(DESTROYING, mActivity.getState());
-        assertTrue(mActivity.finishing);
-        verify(mActivity).destroyImmediately(anyString());
+        assertEquals(DESTROYING, activity.getState());
+        assertTrue(activity.finishing);
+        verify(activity).destroyImmediately(anyString());
     }
 
     /**
@@ -1224,23 +1290,23 @@
      */
     @Test
     public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
+        final ActivityRecord activity = createActivityWithTask();
         // Empty the home stack.
-        final Task homeStack = mActivity.getDisplayArea().getRootHomeTask();
+        final Task homeStack = activity.getDisplayArea().getRootHomeTask();
         homeStack.forAllLeafTasks((t) -> {
             homeStack.removeChild(t, "test");
         }, true /* traverseTopToBottom */);
-        mActivity.finishing = true;
+        activity.finishing = true;
         doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
-        spyOn(mStack);
 
         // Try to destroy the last activity above the home stack.
-        mActivity.destroyIfPossible("test");
+        activity.destroyIfPossible("test");
 
         // Verify that the activity was not actually destroyed, but waits for next one to come up
         // instead.
-        verify(mActivity, never()).destroyImmediately(anyString());
-        assertEquals(FINISHING, mActivity.getState());
-        assertTrue(mActivity.mTaskSupervisor.mFinishingActivities.contains(mActivity));
+        verify(activity, never()).destroyImmediately(anyString());
+        assertEquals(FINISHING, activity.getState());
+        assertTrue(activity.mTaskSupervisor.mFinishingActivities.contains(activity));
     }
 
     /**
@@ -1250,22 +1316,23 @@
      */
     @Test
     public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() {
+        final ActivityRecord activity = createActivityWithTask();
         // Empty the home root task.
-        final Task homeRootTask = mActivity.getDisplayArea().getRootHomeTask();
+        final Task homeRootTask = activity.getDisplayArea().getRootHomeTask();
         homeRootTask.forAllLeafTasks((t) -> {
             homeRootTask.removeChild(t, "test");
         }, true /* traverseTopToBottom */);
-        mActivity.finishing = true;
-        mActivity.mVisibleRequested = true;
-        spyOn(mStack);
+        activity.setState(STARTED, "test");
+        activity.finishing = true;
+        activity.mVisibleRequested = true;
 
         // Try to finish the last activity above the home stack.
-        mActivity.completeFinishing("test");
+        activity.completeFinishing("test");
 
         // Verify that the activity is not destroyed immediately, but waits for next one to come up.
-        verify(mActivity, never()).destroyImmediately(anyString());
-        assertEquals(FINISHING, mActivity.getState());
-        assertTrue(mActivity.mTaskSupervisor.mFinishingActivities.contains(mActivity));
+        verify(activity, never()).destroyImmediately(anyString());
+        assertEquals(FINISHING, activity.getState());
+        assertTrue(activity.mTaskSupervisor.mFinishingActivities.contains(activity));
     }
 
     /**
@@ -1274,10 +1341,11 @@
      */
     @Test
     public void testDestroyImmediately_hadApp_finishing() {
-        mActivity.finishing = true;
-        mActivity.destroyImmediately("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = true;
+        activity.destroyImmediately("test");
 
-        assertEquals(DESTROYING, mActivity.getState());
+        assertEquals(DESTROYING, activity.getState());
     }
 
     /**
@@ -1286,10 +1354,11 @@
      */
     @Test
     public void testDestroyImmediately_hadApp_notFinishing() {
-        mActivity.finishing = false;
-        mActivity.destroyImmediately("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.finishing = false;
+        activity.destroyImmediately("test");
 
-        assertEquals(DESTROYED, mActivity.getState());
+        assertEquals(DESTROYED, activity.getState());
     }
 
     /**
@@ -1298,14 +1367,15 @@
      */
     @Test
     public void testDestroyImmediately_noApp_finishing() {
-        mActivity.app = null;
-        mActivity.finishing = true;
-        final Task task = mActivity.getTask();
+        final ActivityRecord activity = createActivityWithTask();
+        activity.app = null;
+        activity.finishing = true;
+        final Task task = activity.getTask();
 
-        mActivity.destroyImmediately("test");
+        activity.destroyImmediately("test");
 
-        assertEquals(DESTROYED, mActivity.getState());
-        assertNull(mActivity.getTask());
+        assertEquals(DESTROYED, activity.getState());
+        assertNull(activity.getTask());
         assertEquals(0, task.getChildCount());
     }
 
@@ -1315,14 +1385,15 @@
      */
     @Test
     public void testDestroyImmediately_noApp_notFinishing() {
-        mActivity.app = null;
-        mActivity.finishing = false;
-        final Task task = mActivity.getTask();
+        final ActivityRecord activity = createActivityWithTask();
+        activity.app = null;
+        activity.finishing = false;
+        final Task task = activity.getTask();
 
-        mActivity.destroyImmediately("test");
+        activity.destroyImmediately("test");
 
-        assertEquals(DESTROYED, mActivity.getState());
-        assertEquals(task, mActivity.getTask());
+        assertEquals(DESTROYED, activity.getState());
+        assertEquals(task, activity.getTask());
         assertEquals(1, task.getChildCount());
     }
 
@@ -1331,11 +1402,12 @@
      */
     @Test
     public void testSafelyDestroy_nonDestroyable() {
-        doReturn(false).when(mActivity).isDestroyable();
+        final ActivityRecord activity = createActivityWithTask();
+        doReturn(false).when(activity).isDestroyable();
 
-        mActivity.safelyDestroy("test");
+        activity.safelyDestroy("test");
 
-        verify(mActivity, never()).destroyImmediately(anyString());
+        verify(activity, never()).destroyImmediately(anyString());
     }
 
     /**
@@ -1343,29 +1415,31 @@
      */
     @Test
     public void testSafelyDestroy_destroyable() {
-        doReturn(true).when(mActivity).isDestroyable();
+        final ActivityRecord activity = createActivityWithTask();
+        doReturn(true).when(activity).isDestroyable();
 
-        mActivity.safelyDestroy("test");
+        activity.safelyDestroy("test");
 
-        verify(mActivity).destroyImmediately(anyString());
+        verify(activity).destroyImmediately(anyString());
     }
 
     @Test
     public void testRemoveFromHistory() {
-        final Task stack = mActivity.getRootTask();
-        final Task task = mActivity.getTask();
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final Task rootTask = activity.getRootTask();
+        final Task task = activity.getTask();
+        final WindowProcessController wpc = activity.app;
         assertTrue(wpc.hasActivities());
 
-        mActivity.removeFromHistory("test");
+        activity.removeFromHistory("test");
 
-        assertEquals(DESTROYED, mActivity.getState());
-        assertNull(mActivity.app);
-        assertNull(mActivity.getTask());
+        assertEquals(DESTROYED, activity.getState());
+        assertNull(activity.app);
+        assertNull(activity.getTask());
         assertFalse(wpc.hasActivities());
         assertEquals(0, task.getChildCount());
         assertEquals(task.getRootTask(), task);
-        assertEquals(0, stack.getChildCount());
+        assertEquals(0, rootTask.getChildCount());
     }
 
     /**
@@ -1374,8 +1448,9 @@
      */
     @Test(expected = IllegalStateException.class)
     public void testDestroyed_notDestroying() {
-        mActivity.setState(STOPPED, "test");
-        mActivity.destroyed("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(STOPPED, "test");
+        activity.destroyed("test");
     }
 
     /**
@@ -1383,10 +1458,11 @@
      */
     @Test
     public void testDestroyed_destroying() {
-        mActivity.setState(DESTROYING, "test");
-        mActivity.destroyed("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(DESTROYING, "test");
+        activity.destroyed("test");
 
-        verify(mActivity).removeFromHistory(anyString());
+        verify(activity).removeFromHistory(anyString());
     }
 
     /**
@@ -1394,15 +1470,17 @@
      */
     @Test
     public void testDestroyed_destroyed() {
-        mActivity.setState(DESTROYED, "test");
-        mActivity.destroyed("test");
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(DESTROYED, "test");
+        activity.destroyed("test");
 
-        verify(mActivity).removeFromHistory(anyString());
+        verify(activity).removeFromHistory(anyString());
     }
 
     @Test
     public void testActivityOverridesProcessConfig() {
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertFalse(wpc.registeredForDisplayConfigChanges());
 
@@ -1410,18 +1488,19 @@
                 createActivityOnDisplay(false /* defaultDisplay */, null /* process */);
 
         assertTrue(wpc.registeredForActivityConfigChanges());
-        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+        assertEquals(0, activity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
-        assertNotEquals(mActivity.getConfiguration(),
+        assertNotEquals(activity.getConfiguration(),
                 secondaryDisplayActivity.getConfiguration());
     }
 
     @Test
     public void testActivityOverridesProcessConfig_TwoActivities() {
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
         assertTrue(wpc.registeredForActivityConfigChanges());
 
-        final Task firstTaskRecord = mActivity.getTask();
+        final Task firstTaskRecord = activity.getTask();
         final ActivityRecord secondActivityRecord =
                 new ActivityBuilder(mAtm).setTask(firstTaskRecord).setUseProcess(wpc).build();
 
@@ -1432,11 +1511,12 @@
 
     @Test
     public void testActivityOverridesProcessConfig_TwoActivities_SecondaryDisplay() {
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
         assertTrue(wpc.registeredForActivityConfigChanges());
 
         final ActivityRecord secondActivityRecord =
-                new ActivityBuilder(mAtm).setTask(mTask).setUseProcess(wpc).build();
+                new ActivityBuilder(mAtm).setTask(activity.getTask()).setUseProcess(wpc).build();
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
@@ -1445,7 +1525,8 @@
 
     @Test
     public void testActivityOverridesProcessConfig_TwoActivities_DifferentTasks() {
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
         assertTrue(wpc.registeredForActivityConfigChanges());
 
         final ActivityRecord secondActivityRecord =
@@ -1458,10 +1539,11 @@
 
     @Test
     public void testActivityOnCancelFixedRotationTransform() {
-        final DisplayRotation displayRotation = mActivity.mDisplayContent.getDisplayRotation();
+        final ActivityRecord activity = createActivityWithTask();
+        final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
         spyOn(displayRotation);
 
-        final DisplayContent display = mActivity.mDisplayContent;
+        final DisplayContent display = activity.mDisplayContent;
         final int originalRotation = display.getRotation();
 
         // Make {@link DisplayContent#sendNewConfiguration} not apply rotation immediately.
@@ -1469,17 +1551,17 @@
         doReturn((originalRotation + 1) % 4).when(displayRotation).rotationForOrientation(
                 anyInt() /* orientation */, anyInt() /* lastRotation */);
         // Set to visible so the activity can freeze the screen.
-        mActivity.setVisibility(true);
+        activity.setVisibility(true);
 
-        display.rotateInDifferentOrientationIfNeeded(mActivity);
-        display.setFixedRotationLaunchingAppUnchecked(mActivity);
+        display.rotateInDifferentOrientationIfNeeded(activity);
+        display.setFixedRotationLaunchingAppUnchecked(activity);
         displayRotation.updateRotationUnchecked(true /* forceUpdate */);
 
         assertTrue(displayRotation.isRotatingSeamlessly());
 
         // The launching rotated app should not be cleared when waiting for remote rotation.
         display.continueUpdateOrientationForDiffOrienLaunchingApp();
-        assertTrue(display.isFixedRotationLaunchingApp(mActivity));
+        assertTrue(display.isFixedRotationLaunchingApp(activity));
 
         // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
         // remote rotation is completed.
@@ -1487,16 +1569,16 @@
                 anyInt() /* orientation */, anyInt() /* lastRotation */);
         display.updateOrientation();
 
-        final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo();
-        mActivity.finishFixedRotationTransform();
+        final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
+        activity.finishFixedRotationTransform();
         final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
         assertNotNull(rotationAnim);
         rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
 
         // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
         // rotation. There should be a rotation animation to cover the change of activity.
-        verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation);
-        assertTrue(mActivity.isFreezingScreen());
+        verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
+        assertTrue(activity.isFreezingScreen());
         assertFalse(displayRotation.isRotatingSeamlessly());
         assertTrue(rotationAnim.isRotating());
 
@@ -1504,44 +1586,46 @@
         // the rotated activity should also be restored by clearing the transform.
         displayRotation.updateRotationUnchecked(true /* forceUpdate */);
         doReturn(false).when(displayRotation).isWaitingForRemoteRotation();
-        clearInvocations(mActivity);
-        display.setFixedRotationLaunchingAppUnchecked(mActivity);
+        clearInvocations(activity);
+        display.setFixedRotationLaunchingAppUnchecked(activity);
         display.sendNewConfiguration();
 
         assertFalse(display.hasTopFixedRotationLaunchingApp());
-        assertFalse(mActivity.hasFixedRotationTransform());
+        assertFalse(activity.hasFixedRotationTransform());
     }
 
     @Test
     public void testIsSnapshotCompatible() {
+        final ActivityRecord activity = createActivityWithTask();
         final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
-                .setRotation(mActivity.getWindowConfiguration().getRotation())
+                .setRotation(activity.getWindowConfiguration().getRotation())
                 .build();
 
-        assertTrue(mActivity.isSnapshotCompatible(snapshot));
+        assertTrue(activity.isSnapshotCompatible(snapshot));
 
-        setRotatedScreenOrientationSilently(mActivity);
+        setRotatedScreenOrientationSilently(activity);
 
-        assertFalse(mActivity.isSnapshotCompatible(snapshot));
+        assertFalse(activity.isSnapshotCompatible(snapshot));
     }
 
     @Test
     public void testFixedRotationSnapshotStartingWindow() {
+        final ActivityRecord activity = createActivityWithTask();
         // TaskSnapshotSurface requires a fullscreen opaque window.
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
         params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT;
         final TestWindowState w = new TestWindowState(
-                mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
-        mActivity.addWindow(w);
+                mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, activity);
+        activity.addWindow(w);
 
         // Assume the activity is launching in different rotation, and there was an available
         // snapshot accepted by {@link Activity#isSnapshotCompatible}.
         final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
-                .setRotation((mActivity.getWindowConfiguration().getRotation() + 1) % 4)
+                .setRotation((activity.getWindowConfiguration().getRotation() + 1) % 4)
                 .build();
-        setRotatedScreenOrientationSilently(mActivity);
-        mActivity.setVisible(false);
+        setRotatedScreenOrientationSilently(activity);
+        activity.setVisible(false);
 
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
         spyOn(session);
@@ -1553,7 +1637,7 @@
                     any() /* requestedVisibility */, any() /* outFrame */,
                     any() /* outDisplayCutout */, any() /* outInputChannel */,
                     any() /* outInsetsState */, any() /* outActiveControls */);
-            TaskSnapshotSurface.create(mAtm.mWindowManager, mActivity, snapshot);
+            TaskSnapshotSurface.create(mAtm.mWindowManager, activity, snapshot);
         } catch (RemoteException ignored) {
         } finally {
             reset(session);
@@ -1562,8 +1646,8 @@
         // Because the rotation of snapshot and the corresponding top activity are different, fixed
         // rotation should be applied when creating snapshot surface if the display rotation may be
         // changed according to the activity orientation.
-        assertTrue(mActivity.hasFixedRotationTransform());
-        assertTrue(mActivity.mDisplayContent.isFixedRotationLaunchingApp(mActivity));
+        assertTrue(activity.hasFixedRotationTransform());
+        assertTrue(activity.mDisplayContent.isFixedRotationLaunchingApp(activity));
     }
 
     /**
@@ -1596,11 +1680,12 @@
 
     @Test
     public void testActivityReparentChangesProcessOverride() {
-        final WindowProcessController wpc = mActivity.app;
-        final Task initialTask = mActivity.getTask();
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
+        final Task initialTask = activity.getTask();
         final Configuration initialConf =
-                new Configuration(mActivity.getMergedOverrideConfiguration());
-        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+                new Configuration(activity.getMergedOverrideConfiguration());
+        assertEquals(0, activity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
         assertTrue(wpc.registeredForActivityConfigChanges());
 
@@ -1613,22 +1698,23 @@
         assertEquals(newTask.getConfiguration().densityDpi, newConfig.densityDpi);
 
         // Reparent the activity and verify that config override changed.
-        mActivity.reparent(newTask, 0 /* top */, "test");
-        assertEquals(mActivity.getConfiguration().densityDpi, newConfig.densityDpi);
-        assertEquals(mActivity.getMergedOverrideConfiguration().densityDpi, newConfig.densityDpi);
+        activity.reparent(newTask, 0 /* top */, "test");
+        assertEquals(activity.getConfiguration().densityDpi, newConfig.densityDpi);
+        assertEquals(activity.getMergedOverrideConfiguration().densityDpi, newConfig.densityDpi);
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
-        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+        assertEquals(0, activity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
     }
 
     @Test
     public void testActivityReparentDoesntClearProcessOverride_TwoActivities() {
-        final WindowProcessController wpc = mActivity.app;
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
         final Configuration initialConf =
-                new Configuration(mActivity.getMergedOverrideConfiguration());
-        final Task initialTask = mActivity.getTask();
+                new Configuration(activity.getMergedOverrideConfiguration());
+        final Task initialTask = activity.getTask();
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(initialTask)
                 .setUseProcess(wpc).build();
 
@@ -1652,7 +1738,7 @@
         assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
 
         // Reparent the first activity and verify that config override didn't change.
-        mActivity.reparent(newTask, 1 /* top */, "test");
+        activity.reparent(newTask, 1 /* top */, "test");
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, secondActivity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
@@ -1695,67 +1781,90 @@
 
     @Test
     public void testFullscreenWindowCanTurnScreenOn() {
-        mStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        doReturn(true).when(mActivity).getTurnScreenOnFlag();
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        doReturn(true).when(activity).getTurnScreenOnFlag();
 
-        assertTrue(mActivity.canTurnScreenOn());
+        assertTrue(activity.canTurnScreenOn());
     }
 
     @Test
     public void testFreeformWindowCanTurnScreenOn() {
-        mStack.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        doReturn(true).when(mActivity).getTurnScreenOnFlag();
+        final ActivityRecord activity = createActivityWithTask();
+        final Task task = activity.getTask();
+        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        doReturn(true).when(activity).getTurnScreenOnFlag();
 
-        assertTrue(mActivity.canTurnScreenOn());
+        assertTrue(activity.canTurnScreenOn());
     }
 
     @Test
     public void testGetLockTaskLaunchMode() {
+        final ActivityRecord activity = createActivityWithTask();
         final ActivityOptions options = ActivityOptions.makeBasic().setLockTaskEnabled(true);
-        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
         assertEquals(LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED,
-                ActivityRecord.getLockTaskLaunchMode(mActivity.info, options));
+                ActivityRecord.getLockTaskLaunchMode(activity.info, options));
 
-        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
         assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
-                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+                ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
 
-        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
         assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
-                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+                ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
 
-        mActivity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        activity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+        activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
         assertEquals(LOCK_TASK_LAUNCH_MODE_ALWAYS,
-                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+                ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
 
-        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        activity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
         assertEquals(LOCK_TASK_LAUNCH_MODE_NEVER,
-                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+                ActivityRecord.getLockTaskLaunchMode(activity.info, null /*options*/));
 
     }
 
     @Test
     public void testProcessInfoUpdateWhenSetState() {
-        spyOn(mActivity.app);
-        verifyProcessInfoUpdate(RESUMED, true /* shouldUpdate */, true /* activityChange */);
-        verifyProcessInfoUpdate(PAUSED, false /* shouldUpdate */, false /* activityChange */);
-        verifyProcessInfoUpdate(STOPPED, false /* shouldUpdate */, false /* activityChange */);
-        verifyProcessInfoUpdate(STARTED, true /* shouldUpdate */, true /* activityChange */);
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(INITIALIZING, "test");
+        spyOn(activity.app);
+        verifyProcessInfoUpdate(activity, RESUMED,
+                true /* shouldUpdate */, true /* activityChange */);
+        verifyProcessInfoUpdate(activity, PAUSED,
+                false /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(activity, STOPPED,
+                false /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(activity, STARTED,
+                true /* shouldUpdate */, true /* activityChange */);
 
-        mActivity.app.removeActivity(mActivity, true /* keepAssociation */);
-        verifyProcessInfoUpdate(DESTROYING, true /* shouldUpdate */, false /* activityChange */);
-        verifyProcessInfoUpdate(DESTROYED, true /* shouldUpdate */, false /* activityChange */);
+        activity.app.removeActivity(activity, true /* keepAssociation */);
+        verifyProcessInfoUpdate(activity, DESTROYING,
+                true /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(activity, DESTROYED,
+                true /* shouldUpdate */, false /* activityChange */);
     }
 
-    private void verifyProcessInfoUpdate(ActivityState state, boolean shouldUpdate,
-            boolean activityChange) {
-        reset(mActivity.app);
-        mActivity.setState(state, "test");
-        verify(mActivity.app, times(shouldUpdate ? 1 : 0)).updateProcessInfo(anyBoolean(),
+    private void verifyProcessInfoUpdate(ActivityRecord activity, ActivityState state,
+            boolean shouldUpdate, boolean activityChange) {
+        reset(activity.app);
+        activity.setState(state, "test");
+        verify(activity.app, times(shouldUpdate ? 1 : 0)).updateProcessInfo(anyBoolean(),
                 eq(activityChange), anyBoolean(), anyBoolean());
     }
 
+    private ActivityRecord createActivityWithTask() {
+        return new ActivityBuilder(mAtm).setCreateTask(true).setOnTop(true).build();
+    }
+
+    private ActivityRecord createActivityWith2LevelTask() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateParentTask(true).setCreateActivity(true).build();
+        return task.getTopNonFinishingActivity();
+    }
+
     /**
      * Creates an activity on display. For non-default display request it will also create a new
      * display with custom DisplayInfo.
@@ -1769,9 +1878,7 @@
             display = new TestDisplayContent.Builder(mAtm, 2000, 1000).setDensityDpi(300)
                     .setPosition(DisplayContent.POSITION_TOP).build();
         }
-        final Task stack = display.getDefaultTaskDisplayArea()
-                .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
         return new ActivityBuilder(mAtm).setTask(task).setUseProcess(process).build();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index d872eb8..8ccbb8f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -43,6 +43,7 @@
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
 import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
@@ -91,59 +92,58 @@
 @RunWith(WindowTestRunner.class)
 public class ActivityStackTests extends WindowTestsBase {
     private TaskDisplayArea mDefaultTaskDisplayArea;
-    private Task mStack;
-    private Task mTask;
 
     @Before
     public void setUp() throws Exception {
         mDefaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
-        mStack = mDefaultTaskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED,
-                ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        spyOn(mStack);
-        mTask = new TaskBuilder(mSupervisor).setParentTask(mStack).build();
     }
 
     @Test
     public void testResumedActivity() {
-        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
-        assertNull(mStack.getResumedActivity());
+        final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = r.getTask();
+        assertNull(task.getResumedActivity());
         r.setState(RESUMED, "testResumedActivity");
-        assertEquals(r, mStack.getResumedActivity());
+        assertEquals(r, task.getResumedActivity());
         r.setState(PAUSING, "testResumedActivity");
-        assertNull(mStack.getResumedActivity());
+        assertNull(task.getResumedActivity());
     }
 
     @Test
     public void testResumedActivityFromTaskReparenting() {
-        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm)
+                .setCreateTask(true).setParentTask(parentTask).build();
+        final Task task = r.getTask();
         // Ensure moving task between two stacks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
-        assertEquals(r, mStack.getResumedActivity());
+        assertEquals(r, parentTask.getResumedActivity());
 
-        final Task destStack = mDefaultTaskDisplayArea.createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-
-        mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT,
+        final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
+        task.reparent(destStack, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
                 false /* animate */, true /* deferResume*/,
                 "testResumedActivityFromTaskReparenting");
 
-        assertNull(mStack.getResumedActivity());
+        assertNull(parentTask.getResumedActivity());
         assertEquals(r, destStack.getResumedActivity());
     }
 
     @Test
     public void testResumedActivityFromActivityReparenting() {
-        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm)
+                .setCreateTask(true).setParentTask(parentTask).build();
+        final Task task = r.getTask();
         // Ensure moving task between two stacks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
-        assertEquals(r, mStack.getResumedActivity());
+        assertEquals(r, parentTask.getResumedActivity());
 
-        final Task destStack = mDefaultTaskDisplayArea.createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, false, false,
+        final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build();
+        task.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+                false /* animate */, false /* deferResume*/,
                 "testResumedActivityFromActivityReparenting");
 
-        assertNull(mStack.getResumedActivity());
+        assertNull(parentTask.getResumedActivity());
         assertEquals(r, destStack.getResumedActivity());
     }
 
@@ -292,7 +292,7 @@
     public void testStopActivityWhenActivityDestroyed() {
         final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
         r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
-        mStack.moveToFront("testStopActivityWithDestroy");
+        r.getTask().moveToFront("testStopActivityWithDestroy");
         r.stopIfPossible();
         // Mostly testing to make sure there is a crash in the call part, so if we get here we are
         // good-to-go!
@@ -327,7 +327,8 @@
                 targetActivity);
         final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME,
                 aliasActivity);
-        final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(mStack).build();
+        final Task parentTask = new TaskBuilder(mAtm.mTaskSupervisor).build();
+        final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(parentTask).build();
         task.origActivity = alias;
         task.realActivity = target;
         new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity(
@@ -337,14 +338,14 @@
         final ActivityRecord r1 = new ActivityBuilder(mAtm).setComponent(
                 target).setTargetActivity(targetActivity).build();
         RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult();
-        result.process(r1, mStack);
+        result.process(r1, parentTask);
         assertThat(result.mRecord).isNotNull();
 
         // Using alias activity to find task.
         final ActivityRecord r2 = new ActivityBuilder(mAtm).setComponent(
                 alias).setTargetActivity(targetActivity).build();
         result = new RootWindowContainer.FindTaskResult();
-        result.process(r2, mStack);
+        result.process(r2, parentTask);
         assertThat(result.mRecord).isNotNull();
     }
 
@@ -735,8 +736,6 @@
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final Task fullscreenStack = createStackForShouldBeVisibleTest(
@@ -755,8 +754,6 @@
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final Task fullscreenStack = createStackForShouldBeVisibleTest(
@@ -775,8 +772,6 @@
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task fullscreenStack = createStackForShouldBeVisibleTest(
                 mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
@@ -795,8 +790,6 @@
 
     @Test
     public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
@@ -822,8 +815,6 @@
     @Test
     public void
             testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
@@ -846,8 +837,6 @@
 
     @Test
     public void testMoveHomeStackBehindStack_BehindHomeStack() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
                 mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
@@ -869,8 +858,6 @@
 
     @Test
     public void testMoveHomeStackBehindStack() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
-
         final Task fullscreenStack1 = createStackForShouldBeVisibleTest(
                 mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
@@ -1019,11 +1006,12 @@
 
     @Test
     public void testFinishDisabledPackageActivities_FinishAliveActivities() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
         firstActivity.setState(STOPPED, "testFinishDisabledPackageActivities");
         secondActivity.setState(RESUMED, "testFinishDisabledPackageActivities");
-        mStack.mResumedActivity = secondActivity;
+        task.mResumedActivity = secondActivity;
 
         // Note the activities have non-null ActivityRecord.app, so it won't remove directly.
         mRootWindowContainer.mFinishDisabledPackageActivitiesHelper.process(
@@ -1039,10 +1027,11 @@
 
     @Test
     public void testFinishDisabledPackageActivities_RemoveNonAliveActivities() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         // The overlay activity is not in the disabled package but it is in the same task.
-        final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(mTask)
+        final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(task)
                 .setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build();
         // If the task only remains overlay activity, the task should also be removed.
         // See {@link ActivityStack#removeFromHistory}.
@@ -1053,7 +1042,7 @@
         activity.app = null;
         overlayActivity.app = null;
 
-        assertEquals(2, mTask.getChildCount());
+        assertEquals(2, task.getChildCount());
 
         mRootWindowContainer.mFinishDisabledPackageActivitiesHelper.process(
                 activity.packageName, null  /* filterByClasses */, true /* doit */,
@@ -1062,14 +1051,14 @@
         // Although the overlay activity is in another package, the non-overlay activities are
         // removed from the task. Since the overlay activity should be removed as well, the task
         // should be empty.
-        assertFalse(mTask.hasChild());
-        assertFalse(mStack.hasChild());
+        assertFalse(task.hasChild());
     }
 
     @Test
     public void testHandleAppDied() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
 
         // Making the first activity a task overlay means it will be removed from the task's
         // activities as well once second activity is removed as handleAppDied processes the
@@ -1080,17 +1069,17 @@
         // second activity will be immediately removed as it has no state.
         secondActivity.setSavedState(null /* savedState */);
 
-        assertEquals(2, mTask.getChildCount());
+        assertEquals(2, task.getChildCount());
 
         secondActivity.app.handleAppDied();
 
-        assertFalse(mTask.hasChild());
-        assertFalse(mStack.hasChild());
+        assertFalse(task.hasChild());
     }
 
     @Test
     public void testHandleAppDied_RelaunchesAfterCrashDuringWindowingModeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
         activity.launchCount = 1;
@@ -1098,13 +1087,13 @@
 
         activity.app.handleAppDied();
 
-        assertEquals(1, mTask.getChildCount());
-        assertEquals(1, mStack.getChildCount());
+        assertEquals(1, task.getChildCount());
     }
 
     @Test
     public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringWindowingModeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
         activity.launchCount = 3;
@@ -1112,13 +1101,13 @@
 
         activity.app.handleAppDied();
 
-        assertFalse(mTask.hasChild());
-        assertFalse(mStack.hasChild());
+        assertFalse(task.hasChild());
     }
 
     @Test
     public void testHandleAppDied_RelaunchesAfterCrashDuringFreeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
         activity.launchCount = 1;
@@ -1126,13 +1115,13 @@
 
         activity.app.handleAppDied();
 
-        assertEquals(1, mTask.getChildCount());
-        assertEquals(1, mStack.getChildCount());
+        assertEquals(1, task.getChildCount());
     }
 
     @Test
     public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringFreeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
         activity.launchCount = 3;
@@ -1140,22 +1129,22 @@
 
         activity.app.handleAppDied();
 
-        assertFalse(mTask.hasChild());
-        assertFalse(mStack.hasChild());
+        assertFalse(task.hasChild());
     }
 
     @Test
     public void testCompletePauseOnResumeWhilePausingActivity() {
-        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build();
         doReturn(true).when(bottomActivity).attachedToProcess();
-        mStack.mPausingActivity = null;
-        mStack.mResumedActivity = bottomActivity;
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        task.mPausingActivity = null;
+        task.mResumedActivity = bottomActivity;
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
 
-        mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity,
+        task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity,
                 "test");
-        verify(mStack).completePauseLocked(anyBoolean(), eq(topActivity));
+        verify(task).completePauseLocked(anyBoolean(), eq(topActivity));
     }
 
     @Test
@@ -1234,10 +1223,11 @@
 
     @Test
     public void testStackOrderChangedOnRemoveStack() {
+        final Task task = new TaskBuilder(mSupervisor).build();
         StackOrderChangedListener listener = new StackOrderChangedListener();
         mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
         try {
-            mDefaultTaskDisplayArea.removeStack(mStack);
+            mDefaultTaskDisplayArea.removeStack(task);
         } finally {
             mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
         }
@@ -1246,13 +1236,14 @@
 
     @Test
     public void testStackOrderChangedOnAddPositionStack() {
-        mDefaultTaskDisplayArea.removeStack(mStack);
+        final Task task = new TaskBuilder(mSupervisor).build();
+        mDefaultTaskDisplayArea.removeStack(task);
 
         StackOrderChangedListener listener = new StackOrderChangedListener();
         mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener);
         try {
-            mStack.mReparenting = true;
-            mDefaultTaskDisplayArea.addChild(mStack, 0);
+            task.mReparenting = true;
+            mDefaultTaskDisplayArea.addChild(task, 0);
         } finally {
             mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener);
         }
@@ -1284,20 +1275,21 @@
         spyOn(starter);
         doReturn(ActivityManager.START_SUCCESS).when(starter).execute();
 
-        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask)
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task)
                 .setUid(firstActivity.getUid() + 1).build();
         doReturn(starter).when(controller).obtainStarter(eq(firstActivity.intent), anyString());
 
         final IApplicationThread thread = secondActivity.app.getThread();
         secondActivity.app.setThread(null);
         // This should do nothing from a non-attached caller.
-        assertFalse(mStack.navigateUpTo(secondActivity /* source record */,
+        assertFalse(task.navigateUpTo(secondActivity /* source record */,
                 firstActivity.intent /* destIntent */, null /* destGrants */,
                 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
 
         secondActivity.app.setThread(thread);
-        assertTrue(mStack.navigateUpTo(secondActivity /* source record */,
+        assertTrue(task.navigateUpTo(secondActivity /* source record */,
                 firstActivity.intent /* destIntent */, null /* destGrants */,
                 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
         // The firstActivity uses default launch mode, so the activities between it and itself will
@@ -1313,9 +1305,10 @@
         final String affinity = "affinity";
         final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
                 .setUid(Binder.getCallingUid()).setCreateTask(true).build();
-        activity.getTask().affinity = activity.taskAffinity;
+        final Task task = activity.getTask();
+        task.affinity = activity.taskAffinity;
 
-        assertFalse(mStack.shouldUpRecreateTaskLocked(activity, affinity));
+        assertFalse(task.shouldUpRecreateTaskLocked(activity, affinity));
     }
 
     @Test
@@ -1323,21 +1316,23 @@
         final String affinity = "affinity";
         final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
                 .setUid(Binder.getCallingUid()).setCreateTask(true).build();
-        activity.getTask().affinity = activity.taskAffinity;
+        final Task task = activity.getTask();
+        task.affinity = activity.taskAffinity;
         final String fakeAffinity = activity.getUid() + activity.taskAffinity;
 
-        assertTrue(mStack.shouldUpRecreateTaskLocked(activity, fakeAffinity));
+        assertTrue(task.shouldUpRecreateTaskLocked(activity, fakeAffinity));
     }
 
     @Test
     public void testResetTaskWithFinishingActivities() {
-        final ActivityRecord taskTop = new ActivityBuilder(mAtm).setTask(mStack).build();
+        final ActivityRecord taskTop = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = taskTop.getTask();
         // Make all activities in the task are finishing to simulate Task#getTopActivity
         // returns null.
         taskTop.finishing = true;
 
         final ActivityRecord newR = new ActivityBuilder(mAtm).build();
-        final ActivityRecord result = mStack.resetTaskIfNeeded(taskTop, newR);
+        final ActivityRecord result = task.resetTaskIfNeeded(taskTop, newR);
         assertThat(result).isEqualTo(taskTop);
     }
 
@@ -1345,14 +1340,15 @@
     public void testIterateOccludedActivity() {
         final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
         final Consumer<ActivityRecord> handleOccludedActivity = occludedActivities::add;
-        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
-        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         // Top activity occludes bottom activity.
-        doReturn(true).when(mStack).shouldBeVisible(any());
+        doReturn(true).when(task).shouldBeVisible(any());
         assertTrue(topActivity.shouldBeVisible());
         assertFalse(bottomActivity.shouldBeVisible());
 
-        mStack.forAllOccludedActivities(handleOccludedActivity);
+        task.forAllOccludedActivities(handleOccludedActivity);
         assertThat(occludedActivities).containsExactly(bottomActivity);
 
         // Top activity doesn't occlude parent, so the bottom activity is not occluded.
@@ -1360,18 +1356,18 @@
         assertTrue(bottomActivity.shouldBeVisible());
 
         occludedActivities.clear();
-        mStack.forAllOccludedActivities(handleOccludedActivity);
+        task.forAllOccludedActivities(handleOccludedActivity);
         assertThat(occludedActivities).isEmpty();
 
         // A finishing activity should not occlude other activities behind.
-        final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
         finishingActivity.finishing = true;
         doCallRealMethod().when(finishingActivity).occludesParent();
         assertTrue(topActivity.shouldBeVisible());
         assertTrue(bottomActivity.shouldBeVisible());
 
         occludedActivities.clear();
-        mStack.forAllOccludedActivities(handleOccludedActivity);
+        task.forAllOccludedActivities(handleOccludedActivity);
         assertThat(occludedActivities).isEmpty();
     }
 
@@ -1385,8 +1381,9 @@
         // Start 2 activities that their processes have not yet started.
         final ActivityRecord[] activities = new ActivityRecord[2];
         mSupervisor.beginDeferResume();
+        final Task task = new TaskBuilder(mSupervisor).build();
         for (int i = 0; i < activities.length; i++) {
-            final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
+            final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
             activities[i] = r;
             doReturn(null).when(mAtm).getProcessController(
                     eq(r.processName), eq(r.info.applicationInfo.uid));
@@ -1405,7 +1402,7 @@
         // Assume the top activity is going to resume and
         // {@link RootWindowContainer#cancelInitializingActivities} should clear the unknown
         // visibility records that are occluded.
-        mStack.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+        task.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
         // Assume the top activity relayouted, just remove it directly.
         unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
         // All unresolved records should be removed.
@@ -1414,15 +1411,16 @@
 
     @Test
     public void testNonTopVisibleActivityNotResume() {
+        final Task task = new TaskBuilder(mSupervisor).build();
         final ActivityRecord nonTopVisibleActivity =
-                new ActivityBuilder(mAtm).setTask(mTask).build();
-        new ActivityBuilder(mAtm).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
         doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
         doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
                 anyBoolean());
 
-        mStack.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+        task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
                 false /* preserveWindows */);
         verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
                 anyBoolean());
@@ -1436,16 +1434,17 @@
     private void verifyShouldSleepActivities(boolean focusedStack,
             boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
             boolean expected) {
+        final Task task = new TaskBuilder(mSupervisor).build();
         final DisplayContent display = mock(DisplayContent.class);
         final KeyguardController keyguardController = mSupervisor.getKeyguardController();
         display.isDefaultDisplay = isDefaultDisplay;
 
-        mStack.mDisplayContent = display;
+        task.mDisplayContent = display;
         doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
         doReturn(displaySleeping).when(display).isSleeping();
-        doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay();
+        doReturn(focusedStack).when(task).isFocusedStackOnDisplay();
 
-        assertEquals(expected, mStack.shouldSleepActivities());
+        assertEquals(expected, task.shouldSleepActivities());
     }
 
     private static class StackOrderChangedListener
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f607448..bded3f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -126,6 +126,7 @@
     private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude";
     private static final int UNIMPORTANT_UID = 12345;
     private static final int UNIMPORTANT_UID2 = 12346;
+    private static final int CURRENT_IME_UID = 12347;
 
     @Before
     public void setUp() throws Exception {
@@ -315,6 +316,12 @@
         return prepareStarter(launchFlags, mockGetLaunchStack, LAUNCH_MULTIPLE);
     }
 
+    private void setupImeWindow() {
+        final WindowState imeWindow = createWindow(null, W_INPUT_METHOD,
+                "mImeWindow", CURRENT_IME_UID);
+        mDisplayContent.mInputMethodWindow = imeWindow;
+    }
+
     /**
      * Creates a {@link ActivityStarter} with default parameters and necessary mocks.
      *
@@ -654,6 +661,14 @@
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
                 false, false, false, false, true);
+
+        setupImeWindow();
+        runAndVerifyBackgroundActivityStartsSubtest(
+                "disallowed_callingPackageNameIsIme_notAborted", false,
+                CURRENT_IME_UID, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                false, false, false, false, false);
+
     }
 
     private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -680,14 +695,11 @@
             boolean isCallingUidDeviceOwner,
             boolean isPinnedSingleInstance) {
         // window visibility
-        doReturn(callingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
-                .isAnyNonToastWindowVisibleForUid(callingUid);
-        doReturn(realCallingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
-                .isAnyNonToastWindowVisibleForUid(realCallingUid);
-
+        doReturn(callingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(callingUid);
+        doReturn(realCallingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(realCallingUid);
         // process importance
-        doReturn(callingUidProcState).when(mAtm).getUidState(callingUid);
-        doReturn(realCallingUidProcState).when(mAtm).getUidState(realCallingUid);
+        mAtm.mActiveUids.onUidActive(callingUid, callingUidProcState);
+        mAtm.mActiveUids.onUidActive(realCallingUid, realCallingUidProcState);
         // foreground activities
         final IApplicationThread caller = mock(IApplicationThread.class);
         final WindowProcessListener listener = mock(WindowProcessListener.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index f61253c..017ed88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -19,8 +19,6 @@
 import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -44,7 +42,6 @@
 
 import androidx.test.filters.MediumTest;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,13 +55,6 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityTaskSupervisorTests extends WindowTestsBase {
-    private Task mFullscreenTask;
-
-    @Before
-    public void setUp() throws Exception {
-        mFullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-    }
 
     /**
      * Ensures that an activity is removed from the stopping activities list once it is resumed.
@@ -72,7 +62,7 @@
     @Test
     public void testStoppingActivityRemovedWhenResumed() {
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
-                .setTask(mFullscreenTask).build();
+                .setCreateTask(true).build();
         mSupervisor.mStoppingActivities.add(firstActivity);
 
         firstActivity.completeResumeLocked();
@@ -86,7 +76,7 @@
     @Test
     public void testReportWaitingActivityLaunchedIfNeeded() {
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
-                .setTask(mFullscreenTask).build();
+                .setCreateTask(true).build();
 
         final WaitResult taskToFrontWait = new WaitResult();
         mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
@@ -153,7 +143,7 @@
     @Test
     public void testNotifyTaskFocusChanged() {
         final ActivityRecord fullScreenActivityA = new ActivityBuilder(mAtm).setCreateTask(true)
-                .setParentTask(mFullscreenTask).build();
+                .build();
         final Task taskA = fullScreenActivityA.getTask();
 
         final TaskChangeNotificationController taskChangeNotifier =
@@ -166,7 +156,7 @@
         reset(taskChangeNotifier);
 
         final ActivityRecord fullScreenActivityB = new ActivityBuilder(mAtm).setCreateTask(true)
-                .setParentTask(mFullscreenTask).build();
+                .build();
         final Task taskB = fullScreenActivityB.getTask();
 
         mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 87a5985..91b9449 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -53,14 +53,12 @@
 @RunWith(WindowTestRunner.class)
 public class AppChangeTransitionTests extends WindowTestsBase {
 
-    private Task mStack;
     private Task mTask;
     private ActivityRecord mActivity;
 
     public void setUpOnDisplay(DisplayContent dc) {
         mActivity = createActivityRecord(dc, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
         mTask = mActivity.getTask();
-        mStack = mTask.getRootTask();
 
         // Set a remote animator with snapshot disabled. Snapshots don't work in wmtests.
         RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
@@ -143,7 +141,7 @@
         // Reparenting to a display with different windowing mode may trigger
         // a change transition internally, but it should be cleaned-up once
         // the display change is complete.
-        mStack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
+        mTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index f77454d..d899ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -65,7 +65,6 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -82,94 +81,87 @@
 @RunWith(WindowTestRunner.class)
 public class AppWindowTokenTests extends WindowTestsBase {
 
-    Task mStack;
-    Task mTask;
-    ActivityRecord mActivity;
-
     private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
 
-    @Before
-    public void setUp() throws Exception {
-        mStack = createTaskStackOnDisplay(mDisplayContent);
-        mTask = createTaskInStack(mStack, 0 /* userId */);
-        mActivity = createNonAttachedActivityRecord(mDisplayContent);
-
-        mTask.addChild(mActivity, 0);
-    }
-
     @Test
     @Presubmit
     public void testAddWindow_Order() {
-        assertEquals(0, mActivity.getChildCount());
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        assertEquals(0, activity.getChildCount());
 
-        final WindowState win1 = createWindow(null, TYPE_APPLICATION, mActivity, "win1");
-        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mActivity,
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, activity, "win1");
+        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
                 "startingWin");
-        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "baseWin");
-        final WindowState win4 = createWindow(null, TYPE_APPLICATION, mActivity, "win4");
+        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "baseWin");
+        final WindowState win4 = createWindow(null, TYPE_APPLICATION, activity, "win4");
 
         // Should not contain the windows that were added above.
-        assertEquals(4, mActivity.getChildCount());
-        assertTrue(mActivity.mChildren.contains(win1));
-        assertTrue(mActivity.mChildren.contains(startingWin));
-        assertTrue(mActivity.mChildren.contains(baseWin));
-        assertTrue(mActivity.mChildren.contains(win4));
+        assertEquals(4, activity.getChildCount());
+        assertTrue(activity.mChildren.contains(win1));
+        assertTrue(activity.mChildren.contains(startingWin));
+        assertTrue(activity.mChildren.contains(baseWin));
+        assertTrue(activity.mChildren.contains(win4));
 
         // The starting window should be on-top of all other windows.
-        assertEquals(startingWin, mActivity.mChildren.peekLast());
+        assertEquals(startingWin, activity.mChildren.peekLast());
 
         // The base application window should be below all other windows.
-        assertEquals(baseWin, mActivity.mChildren.peekFirst());
-        mActivity.removeImmediately();
+        assertEquals(baseWin, activity.mChildren.peekFirst());
+        activity.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testFindMainWindow() {
-        assertNull(mActivity.findMainWindow());
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        assertNull(activity.findMainWindow());
 
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window1");
-        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mActivity, "window11");
-        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mActivity, "window12");
-        assertEquals(window1, mActivity.findMainWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1");
+        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window11");
+        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, activity, "window12");
+        assertEquals(window1, activity.findMainWindow());
         window1.mAnimatingExit = true;
-        assertEquals(window1, mActivity.findMainWindow());
-        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mActivity,
+        assertEquals(window1, activity.findMainWindow());
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, activity,
                 "window2");
-        assertEquals(window2, mActivity.findMainWindow());
-        mActivity.removeImmediately();
+        assertEquals(window2, activity.findMainWindow());
+        activity.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testGetTopFullscreenOpaqueWindow() {
-        assertNull(mActivity.getTopFullscreenOpaqueWindow());
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        assertNull(activity.getTopFullscreenOpaqueWindow());
 
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window1");
-        final WindowState window11 = createWindow(null, TYPE_APPLICATION, mActivity, "window11");
-        final WindowState window12 = createWindow(null, TYPE_APPLICATION, mActivity, "window12");
-        assertEquals(window12, mActivity.getTopFullscreenOpaqueWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1");
+        final WindowState window11 = createWindow(null, TYPE_APPLICATION, activity, "window11");
+        final WindowState window12 = createWindow(null, TYPE_APPLICATION, activity, "window12");
+        assertEquals(window12, activity.getTopFullscreenOpaqueWindow());
         window12.mAttrs.width = 500;
-        assertEquals(window11, mActivity.getTopFullscreenOpaqueWindow());
+        assertEquals(window11, activity.getTopFullscreenOpaqueWindow());
         window11.mAttrs.width = 500;
-        assertEquals(window1, mActivity.getTopFullscreenOpaqueWindow());
+        assertEquals(window1, activity.getTopFullscreenOpaqueWindow());
         window1.mAttrs.alpha = 0f;
-        assertNull(mActivity.getTopFullscreenOpaqueWindow());
-        mActivity.removeImmediately();
+        assertNull(activity.getTopFullscreenOpaqueWindow());
+        activity.removeImmediately();
     }
 
     @UseTestDisplay(addWindows = W_ACTIVITY)
     @Test
     @FlakyTest(bugId = 131005232)
     public void testLandscapeSeascapeRotationByApp() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final TestWindowState appWindow = createWindowState(attrs, mActivity);
-        mActivity.addWindow(appWindow);
+        final TestWindowState appWindow = createWindowState(attrs, activity);
+        activity.addWindow(appWindow);
 
         // Set initial orientation and update.
-        mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         mDisplayContent.updateOrientation(
                 mDisplayContent.getRequestedOverrideConfiguration(),
                 null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
@@ -177,7 +169,7 @@
         appWindow.mResizeReported = false;
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
-        mActivity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+        activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
         mDisplayContent.updateOrientation(
                 mDisplayContent.getRequestedOverrideConfiguration(),
                 null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
@@ -192,14 +184,17 @@
     @UseTestDisplay(addWindows = W_ACTIVITY)
     @Test
     public void testLandscapeSeascapeRotationByPolicy() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
         // This instance has been spied in {@link TestDisplayContent}.
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("RotationByPolicy");
-        final TestWindowState appWindow = createWindowState(attrs, mActivity);
-        mActivity.addWindow(appWindow);
+        final TestWindowState appWindow = createWindowState(attrs, activity);
+        activity.addWindow(appWindow);
 
         // Set initial orientation and update.
         performRotation(displayRotation, Surface.ROTATION_90);
@@ -220,48 +215,53 @@
     @Test
     @Presubmit
     public void testGetOrientation() {
-        mActivity.setVisible(true);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.setVisible(true);
 
-        mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        mActivity.setOccludesParent(false);
+        activity.setOccludesParent(false);
         // Can specify orientation if app doesn't occludes parent.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mActivity.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
 
-        mActivity.setOccludesParent(true);
-        mActivity.setVisible(false);
+        activity.setOccludesParent(true);
+        activity.setVisible(false);
         // Can not specify orientation if app isn't visible even though it occludes parent.
-        assertEquals(SCREEN_ORIENTATION_UNSET, mActivity.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
-                mActivity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+                activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
     }
 
     @Test
     @Presubmit
     public void testKeyguardFlagsDuringRelaunch() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
         attrs.setTitle("AppWindow");
-        final TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, activity);
 
         // Add window with show when locked flag
-        mActivity.addWindow(appWindow);
-        assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+        activity.addWindow(appWindow);
+        assertTrue(activity.containsShowWhenLockedWindow()
+                && activity.containsDismissKeyguardWindow());
 
         // Start relaunching
-        mActivity.startRelaunching();
-        assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+        activity.startRelaunching();
+        assertTrue(activity.containsShowWhenLockedWindow()
+                && activity.containsDismissKeyguardWindow());
 
         // Remove window and make sure that we still report back flag
-        mActivity.removeChild(appWindow);
-        assertTrue(mActivity.containsShowWhenLockedWindow() && mActivity.containsDismissKeyguardWindow());
+        activity.removeChild(appWindow);
+        assertTrue(activity.containsShowWhenLockedWindow()
+                && activity.containsDismissKeyguardWindow());
 
         // Finish relaunching and ensure flag is now not reported
-        mActivity.finishRelaunching();
-        assertFalse(
-                mActivity.containsShowWhenLockedWindow() || mActivity.containsDismissKeyguardWindow());
+        activity.finishRelaunching();
+        assertFalse(activity.containsShowWhenLockedWindow()
+                || activity.containsDismissKeyguardWindow());
     }
 
     @Test
@@ -281,17 +281,18 @@
 
     @Test
     public void testSetOrientation() {
-        mActivity.setVisible(true);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.setVisible(true);
 
         // Assert orientation is unspecified to start.
-        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.getOrientation());
 
-        mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mActivity.getOrientation());
+        activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, activity.getOrientation());
 
-        mDisplayContent.removeAppToken(mActivity.token);
+        mDisplayContent.removeAppToken(activity.token);
         // Assert orientation is unset to after container is removed.
-        assertEquals(SCREEN_ORIENTATION_UNSET, mActivity.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
 
         // Reset display frozen state
         mWm.mDisplayFrozen = false;
@@ -300,14 +301,15 @@
     @UseTestDisplay
     @Test
     public void testRespectTopFullscreenOrientation() {
-        final Configuration displayConfig = mActivity.mDisplayContent.getConfiguration();
-        final Configuration activityConfig = mActivity.getConfiguration();
-        mActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Configuration displayConfig = activity.mDisplayContent.getConfiguration();
+        final Configuration activityConfig = activity.getConfiguration();
+        activity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
         assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
 
-        final ActivityRecord topActivity = createActivityRecord(mTask);
+        final ActivityRecord topActivity = createActivityRecord(activity.getTask());
         topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, displayConfig.orientation);
@@ -322,32 +324,36 @@
     @UseTestDisplay
     @Test
     public void testReportOrientationChange() {
-        mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
+        activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         mDisplayContent.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
-        reset(mTask);
-        mActivity.reportDescendantOrientationChangeIfNeeded();
-        verify(mTask).onConfigurationChanged(any(Configuration.class));
+        reset(task);
+        activity.reportDescendantOrientationChangeIfNeeded();
+        verify(task).onConfigurationChanged(any(Configuration.class));
     }
 
     @Test
     @FlakyTest(bugId = 131176283)
     public void testCreateRemoveStartingWindow() {
-        mActivity.addStartingWindow(mPackageName,
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        activity.addStartingWindow(mPackageName,
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
                 false);
         waitUntilHandlersIdle();
-        assertHasStartingWindow(mActivity);
-        mActivity.removeStartingWindow();
+        assertHasStartingWindow(activity);
+        activity.removeStartingWindow();
         waitUntilHandlersIdle();
-        assertNoStartingWindow(mActivity);
+        assertNoStartingWindow(activity);
     }
 
     @Test
     public void testAddRemoveRace() {
         // There was once a race condition between adding and removing starting windows
-        final ActivityRecord appToken = createIsolatedTestActivityRecord();
+        final ActivityRecord appToken = new ActivityBuilder(mAtm).setCreateTask(true).build();
         for (int i = 0; i < 1000; i++) {
             appToken.addStartingWindow(mPackageName,
                     android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
@@ -360,8 +366,8 @@
 
     @Test
     public void testTransferStartingWindow() {
-        final ActivityRecord activity1 = createIsolatedTestActivityRecord();
-        final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity1.addStartingWindow(mPackageName,
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
                 false);
@@ -376,8 +382,8 @@
 
     @Test
     public void testTransferStartingWindowWhileCreating() {
-        final ActivityRecord activity1 = createIsolatedTestActivityRecord();
-        final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
         ((TestWindowManagerPolicy) activity1.mWmService.mPolicy).setRunnableWhenAddingSplashScreen(
                 () -> {
                     // Surprise, ...! Transfer window in the middle of the creation flow.
@@ -396,8 +402,8 @@
 
     @Test
     public void testTransferStartingWindowCanAnimate() {
-        final ActivityRecord activity1 = createIsolatedTestActivityRecord();
-        final ActivityRecord activity2 = createIsolatedTestActivityRecord();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity1.addStartingWindow(mPackageName,
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
                 false);
@@ -419,34 +425,34 @@
 
     @Test
     public void testTransferStartingWindowFromFinishingActivity() {
-        mActivity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */,
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity.getTask();
+        activity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */,
                 "Test", 0 /* labelRes */, 0 /* icon */, 0 /* logo */, 0 /* windowFlags */,
                 null /* transferFrom */, true /* newTask */, true /* taskSwitch */,
                 false /* processRunning */, false /* allowTaskSnapshot */,
                 false /* activityCreate */);
         waitUntilHandlersIdle();
-        assertHasStartingWindow(mActivity);
-        mActivity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;
+        assertHasStartingWindow(activity);
+        activity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;
 
-        doCallRealMethod().when(mStack).startActivityLocked(
+        doCallRealMethod().when(task).startActivityLocked(
                 any(), any(), anyBoolean(), anyBoolean(), any());
         // Make mVisibleSetFromTransferredStartingWindow true.
-        final ActivityRecord middle = new ActivityBuilder(mWm.mAtmService)
-                .setTask(mTask).build();
-        mStack.startActivityLocked(middle, null /* focusedTopActivity */,
+        final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build();
+        task.startActivityLocked(middle, null /* focusedTopActivity */,
                 false /* newTask */, false /* keepCurTransition */, null /* options */);
         middle.makeFinishingLocked();
 
-        assertNull(mActivity.mStartingWindow);
+        assertNull(activity.mStartingWindow);
         assertHasStartingWindow(middle);
 
-        final ActivityRecord top = new ActivityBuilder(mWm.mAtmService)
-                .setTask(mTask).build();
+        final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).build();
         // Expect the visibility should be updated to true when transferring starting window from
         // a visible activity.
         top.setVisible(false);
         // The finishing middle should be able to transfer starting window to top.
-        mStack.startActivityLocked(top, null /* focusedTopActivity */,
+        task.startActivityLocked(top, null /* focusedTopActivity */,
                 false /* newTask */, false /* keepCurTransition */, null /* options */);
 
         assertNull(middle.mStartingWindow);
@@ -459,10 +465,12 @@
 
     @Test
     public void testTransferStartingWindowSetFixedRotation() {
-        final ActivityRecord topActivity = createTestActivityRecordForGivenTask(mTask);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity.getTask();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.setVisible(false);
-        mTask.positionChildAt(topActivity, POSITION_TOP);
-        mActivity.addStartingWindow(mPackageName,
+        task.positionChildAt(topActivity, POSITION_TOP);
+        activity.addStartingWindow(mPackageName,
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
                 false);
         waitUntilHandlersIdle();
@@ -470,38 +478,25 @@
         // Make activities to have different rotation from it display and set fixed rotation
         // transform to activity1.
         int rotation = (mDisplayContent.getRotation() + 1) % 4;
-        mDisplayContent.setFixedRotationLaunchingApp(mActivity, rotation);
+        mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
         doReturn(rotation).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(topActivity);
 
         // Make sure the fixed rotation transform linked to activity2 when adding starting window
         // on activity2.
         topActivity.addStartingWindow(mPackageName,
-                android.R.style.Theme, null, "Test", 0, 0, 0, 0, mActivity.appToken.asBinder(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity.appToken.asBinder(),
                 false, false, false, true, false);
         waitUntilHandlersIdle();
         assertTrue(topActivity.hasFixedRotationTransform());
     }
 
-    private ActivityRecord createIsolatedTestActivityRecord() {
-        final Task taskStack = createTaskStackOnDisplay(mDisplayContent);
-        final Task task = createTaskInStack(taskStack, 0 /* userId */);
-        return createTestActivityRecordForGivenTask(task);
-    }
-
-    private ActivityRecord createTestActivityRecordForGivenTask(Task task) {
-        final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
-        task.addChild(activity, 0);
-        waitUntilHandlersIdle();
-        return activity;
-    }
-
     @Test
     public void testTryTransferStartingWindowFromHiddenAboveToken() {
         // Add two tasks on top of each other.
-        final ActivityRecord activityTop = createIsolatedTestActivityRecord();
-        final ActivityRecord activityBottom =
-                createTestActivityRecordForGivenTask(activityTop.getTask());
+        final ActivityRecord activityTop = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activityBottom = new ActivityBuilder(mAtm).build();
+        activityTop.getTask().addChild(activityBottom, 0);
 
         // Add a starting window.
         activityTop.addStartingWindow(mPackageName,
@@ -523,53 +518,58 @@
     @Test
     public void testTransitionAnimationBounds() {
         removeGlobalMinSizeRestriction();
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateParentTask(true).setCreateActivity(true).build();
+        final Task rootTask = task.getRootTask();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
         final Rect stackBounds = new Rect(0, 0, 1000, 600);
         final Rect taskBounds = new Rect(100, 400, 600, 800);
         // Set the bounds and windowing mode to window configuration directly, otherwise the
         // testing setups may be discarded by configuration resolving.
-        mStack.getWindowConfiguration().setBounds(stackBounds);
-        mTask.getWindowConfiguration().setBounds(taskBounds);
-        mActivity.getWindowConfiguration().setBounds(taskBounds);
+        rootTask.getWindowConfiguration().setBounds(stackBounds);
+        task.getWindowConfiguration().setBounds(taskBounds);
+        activity.getWindowConfiguration().setBounds(taskBounds);
 
         // Check that anim bounds for freeform window match task bounds
-        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
-        assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE));
+        task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(task.getBounds(), activity.getAnimationBounds(STACK_CLIP_NONE));
 
         // STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by
         // bounds animation layer.
-        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+        task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        assertEquals(task.getBounds(), activity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
 
         // Even the activity is smaller than task and it is not aligned to the top-left corner of
         // task, the animation bounds the same as task and position should be zero because in real
         // case the letterbox will fill the remaining area in task.
         final Rect halfBounds = new Rect(taskBounds);
         halfBounds.scale(0.5f);
-        mActivity.getWindowConfiguration().setBounds(halfBounds);
+        activity.getWindowConfiguration().setBounds(halfBounds);
         final Point animationPosition = new Point();
-        mActivity.getAnimationPosition(animationPosition);
+        activity.getAnimationPosition(animationPosition);
 
-        assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
+        assertEquals(taskBounds, activity.getAnimationBounds(STACK_CLIP_AFTER_ANIM));
         assertEquals(new Point(0, 0), animationPosition);
 
         // STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
-        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
+        task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        assertEquals(rootTask.getBounds(), activity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM));
     }
 
     @Test
     public void testHasStartingWindow() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
-        final TestWindowState startingWindow = createWindowState(attrs, mActivity);
-        mActivity.startingDisplayed = true;
-        mActivity.addWindow(startingWindow);
-        assertTrue("Starting window should be present", mActivity.hasStartingWindow());
-        mActivity.startingDisplayed = false;
-        assertTrue("Starting window should be present", mActivity.hasStartingWindow());
+        final TestWindowState startingWindow = createWindowState(attrs, activity);
+        activity.startingDisplayed = true;
+        activity.addWindow(startingWindow);
+        assertTrue("Starting window should be present", activity.hasStartingWindow());
+        activity.startingDisplayed = false;
+        assertTrue("Starting window should be present", activity.hasStartingWindow());
 
-        mActivity.removeChild(startingWindow);
-        assertFalse("Starting window should not be present", mActivity.hasStartingWindow());
+        activity.removeChild(startingWindow);
+        assertFalse("Starting window should not be present", activity.hasStartingWindow());
     }
 
     private void assertHasStartingWindow(ActivityRecord atoken) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
index cfe956f..06a6882 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -52,8 +50,6 @@
 
     private DisplayAreaGroup mDisplayAreaGroup;
     private TaskDisplayArea mTaskDisplayArea;
-    private Task mStack;
-    private ActivityRecord mActivity;
 
     @Before
     public void setUp() {
@@ -65,9 +61,6 @@
         mTaskDisplayArea = new TaskDisplayArea(
                 mDisplayContent, mWm, "TDA1", FEATURE_VENDOR_FIRST + 1);
         mDisplayAreaGroup.addChild(mTaskDisplayArea, POSITION_TOP);
-        mStack = mTaskDisplayArea.createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        mActivity = new ActivityBuilder(mAtm).setTask(mStack).build();
         mDisplayContent.setLastFocusedTaskDisplayArea(mTaskDisplayArea);
     }
 
@@ -91,28 +84,31 @@
 
     @Test
     public void testGetRequestedOrientationForDisplay() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setTaskDisplayArea(mTaskDisplayArea).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
         doReturn(true).when(mDisplayContent).onDescendantOrientationChanged(any(), any());
-        mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+        activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
         // Display is portrait, DisplayAreaGroup inherits that
         mDisplayContent.setBounds(0, 0, 600, 900);
 
         assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
-        assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+        assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
                 .isEqualTo(ORIENTATION_PORTRAIT);
 
         // DisplayAreaGroup is landscape, different from Display
         mDisplayAreaGroup.setBounds(0, 0, 600, 450);
 
         assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
-        assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+        assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
                 .isEqualTo(ORIENTATION_LANDSCAPE);
 
         // DisplayAreaGroup is portrait, same as Display
         mDisplayAreaGroup.setBounds(0, 0, 300, 900);
 
         assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
-        assertThat(mActivity.getRequestedConfigurationOrientation(true /* forDisplay */))
+        assertThat(activity.getRequestedConfigurationOrientation(true /* forDisplay */))
                 .isEqualTo(ORIENTATION_PORTRAIT);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 62aa02f..42193c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -17,11 +17,6 @@
 package com.android.server.wm;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -35,7 +30,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -58,57 +52,6 @@
 @RunWith(WindowTestRunner.class)
 public class RootWindowContainerTests extends WindowTestsBase {
 
-    private static final int FAKE_CALLING_UID = 667;
-
-    @Test
-    public void testIsAnyNonToastWindowVisibleForUid_oneToastOneAppStartOneNonToastBothVisible() {
-        final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
-        final WindowState appStart = createWindow(null, TYPE_APPLICATION_STARTING, "appStarting",
-                FAKE_CALLING_UID);
-        toastyToast.mHasSurface = true;
-        app.mHasSurface = true;
-        appStart.mHasSurface = true;
-
-        assertTrue(toastyToast.isVisibleNow());
-        assertTrue(app.isVisibleNow());
-        assertTrue(appStart.isVisibleNow());
-        assertTrue(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
-    }
-
-    @Test
-    public void testIsAnyNonToastWindowVisibleForUid_onlyToastVisible() {
-        final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
-        toastyToast.mHasSurface = true;
-
-        assertTrue(toastyToast.isVisibleNow());
-        assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
-    }
-
-    @Test
-    public void testIsAnyNonToastWindowVisibleForUid_onlyAppStartingVisible() {
-        final WindowState appStart = createWindow(null, TYPE_APPLICATION_STARTING, "appStarting",
-                FAKE_CALLING_UID);
-        appStart.mHasSurface = true;
-
-        assertTrue(appStart.isVisibleNow());
-        assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
-    }
-
-    @Test
-    public void testIsAnyNonToastWindowVisibleForUid_aFewNonToastButNoneVisible() {
-        final WindowState statusBar =
-                createWindow(null, TYPE_STATUS_BAR, "statusBar", FAKE_CALLING_UID);
-        final WindowState notificationShade = createWindow(null, TYPE_NOTIFICATION_SHADE,
-                "notificationShade", FAKE_CALLING_UID);
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
-
-        assertFalse(statusBar.isVisibleNow());
-        assertFalse(notificationShade.isVisibleNow());
-        assertFalse(app.isVisibleNow());
-        assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
-    }
-
     @Test
     public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() {
         assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
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 2326237..a4bf594 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -72,13 +72,11 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class SizeCompatTests extends WindowTestsBase {
-    private Task mStack;
     private Task mTask;
     private ActivityRecord mActivity;
 
     private void setUpApp(DisplayContent display) {
-        mStack = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
-        mTask = mStack.getBottomMostTask();
+        mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
         mActivity = mTask.getTopNonFinishingActivity();
     }
 
@@ -97,7 +95,7 @@
         prepareUnresizable(1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
 
         final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
-        resizeDisplay(mStack.mDisplayContent, 600, 1200);
+        resizeDisplay(mTask.mDisplayContent, 600, 1200);
         // The visible activity should recompute configuration according to the last parent bounds.
         mAtm.restartActivityProcessIfVisible(mActivity.appToken);
 
@@ -196,7 +194,7 @@
         final int originalDpi = mActivity.getConfiguration().densityDpi;
 
         // Move the non-resizable activity to the new display.
-        mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+        mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
 
         assertEquals(originalBounds.width(), mActivity.getBounds().width());
         assertEquals(originalBounds.height(), mActivity.getBounds().height());
@@ -321,7 +319,7 @@
                 .setCanRotate(false).setNotch(notchHeight).build();
 
         // Move the non-resizable activity to the new display.
-        mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+        mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
         // The configuration bounds [820, 0 - 1820, 2500] should keep the same.
         assertEquals(origWidth, configBounds.width());
         assertEquals(origHeight, configBounds.height());
@@ -363,7 +361,7 @@
 
         // Although the activity is fixed orientation, force rotate the display.
         rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
-        assertEquals(ROTATION_270, mStack.getWindowConfiguration().getRotation());
+        assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());
 
         assertEquals(origBounds.width(), currentBounds.width());
         // The notch is on horizontal side, so current height changes from 1460 to 1400.
@@ -436,7 +434,7 @@
     public void testResetNonVisibleActivity() {
         setUpDisplaySizeWithApp(1000, 2500);
         prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
-        final DisplayContent display = mStack.mDisplayContent;
+        final DisplayContent display = mTask.mDisplayContent;
         // Resize the display so the activity is in size compatibility mode.
         resizeDisplay(display, 900, 1800);
 
@@ -488,7 +486,7 @@
                 });
 
         // Resize the display so that the activity exercises size-compat mode.
-        resizeDisplay(mStack.mDisplayContent, 1000, 2500);
+        resizeDisplay(mTask.mDisplayContent, 1000, 2500);
 
         // Expect the exact token when the activity is in size compatibility mode.
         assertEquals(1, compatTokens.size());
@@ -501,7 +499,7 @@
         activity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
         activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
-        mStack.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
+        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
 
         // Expect null token when switching to non-size-compat mode activity.
         assertEquals(1, compatTokens.size());
@@ -525,13 +523,13 @@
 
         // The non-resizable activity should not be size compat because it is on a resizable task
         // in multi-window mode.
-        mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
         assertFalse(activity.shouldUseSizeCompatMode());
 
         // The non-resizable activity should not be size compat because the display support
         // changing windowing mode from fullscreen to freeform.
-        mStack.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
-        mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
         assertFalse(activity.shouldUseSizeCompatMode());
     }
 
@@ -783,7 +781,7 @@
         assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
 
         final Rect activityBounds = new Rect(mActivity.getBounds());
-        mStack.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+        mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
 
         // App still in size compat, and the bounds don't change.
         verify(mActivity, never()).clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 4bd8edd..28ba710 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -70,23 +70,22 @@
 @RunWith(WindowTestRunner.class)
 public class TaskDisplayAreaTests extends WindowTestsBase {
 
-    private Task mPinnedStack;
+    private Task mPinnedTask;
 
     @Before
     public void setUp() throws Exception {
-        mPinnedStack = createTaskStackOnDisplay(
+        mPinnedTask = createTaskStackOnDisplay(
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         // Stack should contain visible app window to be considered visible.
-        final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
-        assertFalse(mPinnedStack.isVisible());
+        assertFalse(mPinnedTask.isVisible());
         final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent);
-        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
-        assertTrue(mPinnedStack.isVisible());
+        mPinnedTask.addChild(pinnedApp, 0 /* addPos */);
+        assertTrue(mPinnedTask.isVisible());
     }
 
     @After
     public void tearDown() throws Exception {
-        mPinnedStack.removeImmediately();
+        mPinnedTask.removeImmediately();
     }
 
     @Test
@@ -118,19 +117,19 @@
 
         final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
         final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
         assertThat(pinnedStackPos).isGreaterThan(stack2Pos);
         assertThat(stack2Pos).isGreaterThan(stack1Pos);
 
-        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedTask, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
 
-        taskStackContainer.positionChildAt(1, mPinnedStack, false);
+        taskStackContainer.positionChildAt(1, mPinnedTask, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
     }
 
     @Test
@@ -141,16 +140,16 @@
         final WindowContainer taskStackContainer = stack1.getParent();
 
         final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
         assertThat(pinnedStackPos).isGreaterThan(stackPos);
 
         taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
 
         taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 75d2c51..1d498bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -35,6 +35,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -731,6 +732,39 @@
                 handle.inputFeatures);
     }
 
+    @Test
+    public void testHasActiveVisibleWindow() {
+        final int uid = ActivityBuilder.DEFAULT_FAKE_UID;
+        mAtm.mActiveUids.onUidActive(uid, 0 /* any proc state */);
+
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid);
+        app.mActivityRecord.setVisible(false);
+        app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+        assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+        app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+        assertTrue(mAtm.hasActiveVisibleWindow(uid));
+
+        // Make the activity invisible and add a visible toast. The uid should have no active
+        // visible window because toast can be misused by legacy app to bypass background check.
+        app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+        final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay", uid);
+        final WindowState toast = createWindow(null, TYPE_TOAST, app.mToken, "toast", uid);
+        toast.onSurfaceShownChanged(true);
+        assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+        // Though starting window should belong to system. Make sure it is ignored to avoid being
+        // allow-list unexpectedly, see b/129563343.
+        final WindowState starting =
+                createWindow(null, TYPE_APPLICATION_STARTING, app.mToken, "starting", uid);
+        starting.onSurfaceShownChanged(true);
+        assertFalse(mAtm.hasActiveVisibleWindow(uid));
+
+        // Make the application overlay window visible. It should be a valid active visible window.
+        overlay.onSurfaceShownChanged(true);
+        assertTrue(mAtm.hasActiveVisibleWindow(uid));
+    }
+
     @UseTestDisplay(addWindows = W_ACTIVITY)
     @Test
     public void testNeedsRelativeLayeringToIme_notAttached() {
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 ee1034c..8305381 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -637,6 +637,7 @@
      * Builder for creating new activities.
      */
     protected static class ActivityBuilder {
+        static final int DEFAULT_FAKE_UID = 12345;
         // An id appended to the end of the component name to make it unique
         private static int sCurrentActivityId = 0;
 
@@ -647,7 +648,7 @@
         private Task mTask;
         private String mProcessName = "name";
         private String mAffinity;
-        private int mUid = 12345;
+        private int mUid = DEFAULT_FAKE_UID;
         private boolean mCreateTask = false;
         private Task mParentTask;
         private int mActivityFlags;
@@ -840,10 +841,13 @@
                 // to set it somewhere else since we can't mock resources.
                 doReturn(true).when(activity).occludesParent();
                 doReturn(true).when(activity).fillsParent();
+                mTask.addChild(activity);
                 if (mOnTop) {
+                    // Move the task to front after activity added.
+                    // Or {@link TaskDisplayArea#mPreferredTopFocusableStack} could be other stacks
+                    // (e.g. home stack).
                     mTask.moveToFront("createActivity");
                 }
-                mTask.addChild(activity);
                 // Make visible by default...
                 activity.setVisible(true);
             }
@@ -854,7 +858,7 @@
             } else {
                 wpc = new WindowProcessController(mService,
                         aInfo.applicationInfo, mProcessName, mUid,
-                        UserHandle.getUserId(12345), mock(Object.class),
+                        UserHandle.getUserId(mUid), mock(Object.class),
                         mock(WindowProcessListener.class));
                 wpc.setThread(mock(IApplicationThread.class));
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 632ad4c..4bf93a2 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -60,7 +60,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
-import java.util.function.BiFunction;
 
 /**
  * Helper for {@link SoundTrigger} APIs. Supports two types of models:
@@ -118,8 +117,7 @@
 
     private PowerSaveModeListener mPowerSaveModeListener;
 
-    private final BiFunction<Integer, SoundTrigger.StatusListener, SoundTriggerModule>
-            mModuleProvider;
+    private final SoundTriggerModuleProvider mModuleProvider;
 
     // Handler to process call state changes will delay to allow time for the audio
     // and sound trigger HALs to process the end of call notifications
@@ -128,12 +126,32 @@
     private static final int MSG_CALL_STATE_CHANGED = 0;
     private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
 
-    SoundTriggerHelper(Context context,
-            @NonNull BiFunction<Integer, SoundTrigger.StatusListener,
-                    SoundTriggerModule> moduleProvider) {
+    /**
+     * Provider interface for retrieving SoundTriggerModule instances
+     */
+    public interface SoundTriggerModuleProvider {
+        /**
+         * Populate module properties for all available modules
+         *
+         * @param modules List of ModuleProperties to be populated
+         * @return Status int 0 on success.
+         */
+        int listModuleProperties(@NonNull ArrayList<SoundTrigger.ModuleProperties> modules);
+
+        /**
+         * Get SoundTriggerModule based on {@link SoundTrigger.ModuleProperties#getId()}
+         *
+         * @param moduleId Module ID
+         * @param statusListener Client listener to be associated with the returned module
+         * @return Module associated with moduleId
+         */
+        SoundTriggerModule getModule(int moduleId, SoundTrigger.StatusListener statusListener);
+    }
+
+    SoundTriggerHelper(Context context, SoundTriggerModuleProvider moduleProvider) {
         ArrayList <ModuleProperties> modules = new ArrayList<>();
         mModuleProvider = moduleProvider;
-        int status = SoundTrigger.listModules(modules);
+        int status = mModuleProvider.listModuleProperties(modules);
         mContext = context;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -272,7 +290,7 @@
 
     private int prepareForRecognition(ModelData modelData) {
         if (mModule == null) {
-            mModule = mModuleProvider.apply(mModuleProperties.getId(), this);
+            mModule = mModuleProvider.getModule(mModuleProperties.getId(), this);
             if (mModule == null) {
                 Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
                 return STATUS_ERROR;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 2a5bfce..bd678fd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -51,6 +51,7 @@
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.hardware.soundtrigger.SoundTriggerModule;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioRecord;
@@ -219,9 +220,20 @@
         Identity originatorIdentity = IdentityContext.getNonNull();
 
         return new SoundTriggerHelper(mContext,
-                (moduleId, listener) -> SoundTrigger.attachModuleAsMiddleman(moduleId, listener,
-                        null,
-                        middlemanIdentity, originatorIdentity));
+                new SoundTriggerHelper.SoundTriggerModuleProvider() {
+                    @Override
+                    public int listModuleProperties(ArrayList<ModuleProperties> modules) {
+                        return SoundTrigger.listModulesAsMiddleman(modules, middlemanIdentity,
+                                originatorIdentity);
+                    }
+
+                    @Override
+                    public SoundTriggerModule getModule(int moduleId,
+                            SoundTrigger.StatusListener statusListener) {
+                        return SoundTrigger.attachModuleAsMiddleman(moduleId, statusListener, null,
+                                middlemanIdentity, originatorIdentity);
+                    }
+                });
     }
 
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 08b6eda..b785037 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -29,6 +29,7 @@
  * Define capability of a modem group. That is, the capabilities
  * are shared between those modems defined by list of modem IDs.
  *
+ * @hide
  */
 public final class PhoneCapability implements Parcelable {
     // Hardcoded default DSDS capability.
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index 95c69ba..8d49e15 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -29,6 +29,10 @@
 import java.util.Arrays;
 import java.util.Objects;
 
+/**
+ * @hide
+ */
+@SystemApi
 public final class PhysicalChannelConfig implements Parcelable {
 
     // TODO(b/72993578) consolidate these enums in a central location.
@@ -82,7 +86,7 @@
     private int mFrequencyRange;
 
     /**
-     * The absolute radio frequency channel number, {@link #CHANNEL_NUMBER_UNKNOWN} if unknown.
+     * The absolute radio frequency channel number, {@link CHANNEL_NUMBER_UNKNOWN} if unknown.
      */
     private int mChannelNumber;
 
@@ -93,7 +97,7 @@
     private int[] mContextIds;
 
     /**
-     * The physical cell identifier for this cell - PCI, PSC, {@link #PHYSICAL_CELL_ID_UNKNOWN}
+     * The physical cell identifier for this cell - PCI, PSC, {@link PHYSICAL_CELL_ID_UNKNOWN}
      * if unknown.
      */
     private int mPhysicalCellId;
@@ -149,7 +153,7 @@
 
     /**
      * @return the absolute radio frequency channel number for this physical channel,
-     * {@link #CHANNEL_NUMBER_UNKNOWN} if unknown.
+     * {@link CHANNEL_NUMBER_UNKNOWN} if unknown.
      */
     public int getChannelNumber() {
         return mChannelNumber;
@@ -165,7 +169,7 @@
      * In 5G RAN, this value is physical layer cell identity. The range is [0, 1007].
      * Reference: 3GPP TS 38.211 section 7.4.2.1.
      *
-     * @return the physical cell identifier for this cell, {@link #PHYSICAL_CELL_ID_UNKNOWN}
+     * @return the physical cell identifier for this cell, {@link PHYSICAL_CELL_ID_UNKNOWN}
      * if {@link android.telephony.CellInfo#UNAVAILABLE}.
      */
     @IntRange(from = 0, to = 1007)
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 964cf76..572aed3 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -447,6 +447,9 @@
      *  <code>RESULT_RIL_NO_RESOURCES</code><br>
      *  <code>RESULT_RIL_CANCELLED</code><br>
      *  <code>RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
      *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
      *  the sentIntent may include the extra "errorCode" containing a radio technology specific
      *  value, generally only useful for troubleshooting.<br>
@@ -561,6 +564,9 @@
      *  <code>RESULT_RIL_NO_RESOURCES</code><br>
      *  <code>RESULT_RIL_CANCELLED</code><br>
      *  <code>RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
      *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
      *  the sentIntent may include the extra "errorCode" containing a radio technology specific
      *  value, generally only useful for troubleshooting.<br>
@@ -962,6 +968,9 @@
      *  <code>RESULT_RIL_NO_RESOURCES</code><br>
      *  <code>RESULT_RIL_CANCELLED</code><br>
      *  <code>RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
      *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
      *  the sentIntent may include the extra "errorCode" containing a radio technology specific
      *  value, generally only useful for troubleshooting.<br>
@@ -1220,6 +1229,9 @@
      *  <code>RESULT_RIL_NO_RESOURCES</code><br>
      *  <code>RESULT_RIL_CANCELLED</code><br>
      *  <code>RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
      *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
      *  the sentIntent may include the extra "errorCode" containing a radio technology specific
      *  value, generally only useful for troubleshooting.<br>
@@ -1419,6 +1431,9 @@
      *  <code>RESULT_RIL_NO_RESOURCES</code><br>
      *  <code>RESULT_RIL_CANCELLED</code><br>
      *  <code>RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
      *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
      *  the sentIntent may include the extra "errorCode" containing a radio technology specific
      *  value, generally only useful for troubleshooting.<br>
@@ -2298,7 +2313,10 @@
             RESULT_RIL_OPERATION_NOT_ALLOWED,
             RESULT_RIL_NO_RESOURCES,
             RESULT_RIL_CANCELLED,
-            RESULT_RIL_SIM_ABSENT
+            RESULT_RIL_SIM_ABSENT,
+            RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED,
+            RESULT_RIL_ACCESS_BARRED,
+            RESULT_RIL_BLOCKED_DUE_TO_CALL
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Result {}
@@ -2563,6 +2581,21 @@
      */
     public static final int RESULT_RIL_SIM_ABSENT = 120;
 
+    /**
+     * 1X voice and SMS are not allowed simulteneously.
+     */
+    public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121;
+
+    /**
+     * Access is barred.
+     */
+    public static final int RESULT_RIL_ACCESS_BARRED = 122;
+
+    /**
+     * SMS is blocked due to call control, e.g., resource unavailable in the SMR entity.
+     */
+    public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123;
+
     // SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION}
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 683f200..ae2d1ad 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -123,7 +123,6 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -155,7 +154,6 @@
 public class TelephonyManager {
     private static final String TAG = "TelephonyManager";
 
-    private TelephonyRegistryManager mTelephonyRegistryMgr;
     /**
      * To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
      * {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
@@ -5595,22 +5593,12 @@
      * @param events The telephony state(s) of interest to the listener,
      *               as a bitwise-OR combination of {@link PhoneStateListener}
      *               LISTEN_ flags.
-     * @deprecated Use {@link #registerPhoneStateListener(Executor, PhoneStateListener)}.
+     * @deprecated use {@link #listen(long, PhoneStateListener) instead due to the event number
+     *             limit increased to 64.
      */
     @Deprecated
     public void listen(PhoneStateListener listener, int events) {
-        boolean notifyNow = getITelephony() != null;
-        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
-        if (mTelephonyRegistryMgr != null) {
-            if (events != PhoneStateListener.LISTEN_NONE) {
-                mTelephonyRegistryMgr.registerPhoneStateListenerWithEvents(getSubId(),
-                        getOpPackageName(), getAttributionTag(), listener, events, notifyNow);
-            } else {
-                unregisterPhoneStateListener(listener);
-            }
-        } else {
-            throw new IllegalStateException("telephony service is null.");
-        }
+        listen(events, listener);
     }
 
     /**
@@ -5646,21 +5634,18 @@
      *               LISTEN_ flags.
      * @param listener The {@link PhoneStateListener} object to register
      *                 (or unregister)
-     * @deprecated Use {@link #registerPhoneStateListener(Executor, PhoneStateListener)}.
      */
-    @Deprecated
     public void listen(long events, @NonNull PhoneStateListener listener) {
-        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
-        if (mTelephonyRegistryMgr != null) {
-            if (events != PhoneStateListener.LISTEN_NONE) {
-                mTelephonyRegistryMgr.registerPhoneStateListenerWithEvents(getSubId(),
-                        getOpPackageName(), getAttributionTag(), listener,
-                        Long.valueOf(events).intValue(), getITelephony() != null);
-            } else {
-                unregisterPhoneStateListener(listener);
-            }
+        if (mContext == null) return;
+        boolean notifyNow = (getITelephony() != null);
+        TelephonyRegistryManager telephonyRegistry =
+                (TelephonyRegistryManager)
+                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        if (telephonyRegistry != null) {
+            telephonyRegistry.listenForSubscriber(mSubId, getOpPackageName(), getAttributionTag(),
+                    listener, events, notifyNow);
         } else {
-            throw new IllegalStateException("telephony service is null.");
+            Rlog.w(TAG, "telephony registry not ready.");
         }
     }
 
@@ -13215,6 +13200,37 @@
     }
 
     /**
+     * Get which bands the modem's background scan is acting on, specified by
+     * {@link #setSystemSelectionChannels}.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @return a list of {@link RadioAccessSpecifier}, or an empty list if no bands are specified.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @NonNull List<RadioAccessSpecifier> getSystemSelectionChannels() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getSystemSelectionChannels(getSubId());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            if (!isSystemProcess()) {
+                ex.rethrowAsRuntimeException();
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    /**
      * Verifies whether the input MCC/MNC and MVNO correspond to the current carrier.
      *
      * @param mccmnc the carrier's mccmnc that you want to match
@@ -14193,70 +14209,4 @@
 
         return Collections.emptyList();
     }
-
-    /**
-     * Registers a listener object to receive notification of changes
-     * in specified telephony states.
-     * <p>
-     * To register a listener, pass a {@link PhoneStateListener} which implements
-     * interfaces of events. For example,
-     * FakeServiceStateChangedListener extends {@link PhoneStateListener} implements
-     * {@link PhoneStateListener.ServiceStateChangedListener}.
-     *
-     * At registration, and when a specified telephony state changes, the telephony manager invokes
-     * the appropriate callback method on the listener object and passes the current (updated)
-     * values.
-     * <p>
-     *
-     * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
-     * applies to the given subId. Otherwise, applies to
-     * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds,
-     * pass a separate listener object to each TelephonyManager object created with
-     * {@link #createForSubscriptionId}.
-     *
-     * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
-     * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
-     * {@link SecurityException} will be thrown otherwise.
-     *
-     * This API should be used sparingly -- large numbers of listeners will cause system
-     * instability. If a process has registered too many listeners without unregistering them, it
-     * may encounter an {@link IllegalStateException} when trying to register more listeners.
-     *
-     * @param executor The executor of where the callback will execute.
-     * @param listener The {@link PhoneStateListener} object to register.
-     */
-    public void registerPhoneStateListener(@NonNull @CallbackExecutor Executor executor,
-            @NonNull PhoneStateListener listener) {
-        if (executor == null || listener == null) {
-            throw new IllegalStateException("telephony service is null.");
-        }
-        mTelephonyRegistryMgr = (TelephonyRegistryManager)
-                mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
-        if (mTelephonyRegistryMgr != null) {
-            mTelephonyRegistryMgr.registerPhoneStateListener(executor, getSubId(),
-                    getOpPackageName(), getAttributionTag(), listener, getITelephony() != null);
-        } else {
-            throw new IllegalStateException("telephony service is null.");
-        }
-    }
-
-    /**
-     * Unregister an existing {@link PhoneStateListener}.
-     *
-     * @param listener The {@link PhoneStateListener} object to unregister.
-     */
-    public void unregisterPhoneStateListener(@NonNull PhoneStateListener listener) {
-
-        if (mContext == null) {
-            throw new IllegalStateException("telephony service is null.");
-        }
-
-        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
-        if (mTelephonyRegistryMgr != null) {
-            mTelephonyRegistryMgr.unregisterPhoneStateListener(getSubId(), getOpPackageName(),
-                    getAttributionTag(), listener, getITelephony() != null);
-        } else {
-            throw new IllegalStateException("telephony service is null.");
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
rename to telephony/java/android/telephony/data/ApnThrottleStatus.aidl
index d7a3af0..46bc4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountEvaluator.java
+++ b/telephony/java/android/telephony/data/ApnThrottleStatus.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,13 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.classifier;
+/** @hide */
+package android.telephony.data;
 
-public class PointerCountEvaluator {
-    public static float evaluate(int value) {
-        return (value - 1) * (value - 1);
-    }
-}
+parcelable ApnThrottleStatus;
diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.java b/telephony/java/android/telephony/data/ApnThrottleStatus.java
new file mode 100644
index 0000000..51461d1
--- /dev/null
+++ b/telephony/java/android/telephony/data/ApnThrottleStatus.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+
+import java.util.Objects;
+
+/**
+ * Status information regarding the throttle status of an APN type.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ApnThrottleStatus implements Parcelable {
+    /**
+     * The APN type is not throttled.
+     */
+    public static final int THROTTLE_TYPE_NONE = 1;
+
+    /**
+     * The APN type is throttled until {@link android.os.SystemClock#elapsedRealtime()}
+     * has reached {@link ApnThrottleStatus#getThrottleExpiryTimeMillis}
+     */
+    public static final int THROTTLE_TYPE_ELAPSED_TIME = 2;
+
+    /** {@hide} */
+    @IntDef(flag = true, prefix = {"THROTTLE_TYPE_"}, value = {
+            ApnThrottleStatus.THROTTLE_TYPE_NONE,
+            ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME,
+    })
+    public @interface ThrottleType {
+    }
+
+    /**
+     * The framework will not retry the APN type.
+     */
+    public static final int RETRY_TYPE_NONE = 1;
+
+    /**
+     * The next time the framework retries, it will attempt to establish a new connection.
+     */
+    public static final int RETRY_TYPE_NEW_CONNECTION = 2;
+
+    /**
+     * The next time the framework retires, it will retry to handover.
+     */
+    public static final int RETRY_TYPE_HANDOVER = 3;
+
+    /** {@hide} */
+    @IntDef(flag = true, prefix = {"RETRY_TYPE_"}, value = {
+            ApnThrottleStatus.RETRY_TYPE_NONE,
+            ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION,
+            ApnThrottleStatus.RETRY_TYPE_HANDOVER,
+    })
+    public @interface RetryType {
+    }
+
+    private final int mSlotIndex;
+    private final @AccessNetworkConstants.TransportType int mTransportType;
+    private final @Annotation.ApnType int mApnType;
+    private final long mThrottleExpiryTimeMillis;
+    private final @RetryType int mRetryType;
+    private final @ThrottleType int mThrottleType;
+
+    /**
+     * The slot index that the status applies to.
+     *
+     * @return the slot index
+     */
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    /**
+     * The type of transport that the status applies to.
+     *
+     * @return the transport type
+     */
+    @AccessNetworkConstants.TransportType
+    public int getTransportType() {
+        return mTransportType;
+    }
+
+    /**
+     * The APN type that the status applies to.
+     *
+     * @return the apn type
+     */
+    @Annotation.ApnType
+    public int getApnType() {
+        return mApnType;
+    }
+
+    /**
+     * The type of throttle applied to the APN type.
+     *
+     * @return the throttle type
+     */
+    @ThrottleType
+    public int getThrottleType() {
+        return mThrottleType;
+    }
+
+    /**
+     * Indicates the type of request that the framework will make the next time it retries
+     * to call {@link IDataService#setupDataCall}.
+     *
+     * @return the retry type
+     */
+    @RetryType
+    public int getRetryType() {
+        return mRetryType;
+    }
+
+    /**
+     * Gets the time at which the throttle expires.  The value is based off of
+     * {@link SystemClock#elapsedRealtime}.
+     *
+     * This value only applies when the throttle type is set to
+     * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}.
+     *
+     * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely.
+     *
+     * @return the time at which the throttle expires
+     */
+    @ElapsedRealtimeLong
+    public long getThrottleExpiryTimeMillis() {
+        return mThrottleExpiryTimeMillis;
+    }
+
+    private ApnThrottleStatus(int slotIndex,
+            @AccessNetworkConstants.TransportType int transportType,
+            @Annotation.ApnType int apnTypes,
+            @ThrottleType int throttleType,
+            long throttleExpiryTimeMillis,
+            @RetryType int retryType) {
+        mSlotIndex = slotIndex;
+        mTransportType = transportType;
+        mApnType = apnTypes;
+        mThrottleType = throttleType;
+        mThrottleExpiryTimeMillis = throttleExpiryTimeMillis;
+        mRetryType = retryType;
+    }
+
+    private ApnThrottleStatus(@NonNull Parcel source) {
+        mSlotIndex = source.readInt();
+        mTransportType = source.readInt();
+        mApnType = source.readInt();
+        mThrottleExpiryTimeMillis = source.readLong();
+        mRetryType = source.readInt();
+        mThrottleType = source.readInt();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSlotIndex);
+        dest.writeInt(mTransportType);
+        dest.writeInt(mApnType);
+        dest.writeLong(mThrottleExpiryTimeMillis);
+        dest.writeInt(mRetryType);
+        dest.writeInt(mThrottleType);
+    }
+
+    public static final @NonNull Parcelable.Creator<ApnThrottleStatus> CREATOR =
+            new Parcelable.Creator<ApnThrottleStatus>() {
+                @Override
+                public ApnThrottleStatus createFromParcel(@NonNull Parcel source) {
+                    return new ApnThrottleStatus(source);
+                }
+
+                @Override
+                public ApnThrottleStatus[] newArray(int size) {
+                    return new ApnThrottleStatus[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSlotIndex, mApnType, mRetryType, mThrottleType,
+                mThrottleExpiryTimeMillis, mTransportType);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (obj instanceof ApnThrottleStatus) {
+            ApnThrottleStatus other = (ApnThrottleStatus) obj;
+            return this.mSlotIndex == other.mSlotIndex
+                    && this.mApnType == other.mApnType
+                    && this.mRetryType == other.mRetryType
+                    && this.mThrottleType == other.mThrottleType
+                    && this.mThrottleExpiryTimeMillis == other.mThrottleExpiryTimeMillis
+                    && this.mTransportType == other.mTransportType;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ApnThrottleStatus{"
+                + "mSlotIndex=" + mSlotIndex
+                + ", mTransportType=" + mTransportType
+                + ", mApnType=" + ApnSetting.getApnTypeString(mApnType)
+                + ", mThrottleExpiryTimeMillis=" + mThrottleExpiryTimeMillis
+                + ", mRetryType=" + mRetryType
+                + ", mThrottleType=" + mThrottleType
+                + '}';
+    }
+
+    /**
+     * Provides a convenient way to set the fields of an {@link ApnThrottleStatus} when creating a
+     * new instance.
+     *
+     * <p>The example below shows how you might create a new {@code ApnThrottleStatus}:
+     *
+     * <pre><code>
+     *
+     * DataCallResponseApnThrottleStatus = new ApnThrottleStatus.Builder()
+     *     .setSlotIndex(1)
+     *     .setApnType({@link ApnSetting#TYPE_EMERGENCY})
+     *     .setNoThrottle()
+     *     .setRetryType({@link ApnThrottleStatus#RETRY_TYPE_NEW_CONNECTION})
+     *     .build();
+     * </code></pre>
+     */
+    public static final class Builder {
+        private int mSlotIndex;
+        private @AccessNetworkConstants.TransportType int mTransportType;
+        private @Annotation.ApnType int mApnType;
+        private long mThrottleExpiryTimeMillis;
+        private @RetryType int mRetryType;
+        private @ThrottleType int mThrottleType;
+        public static final long NO_THROTTLE_EXPIRY_TIME =
+                DataCallResponse.RETRY_DURATION_UNDEFINED;
+
+        /**
+         * Default constructor for the Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Set the slot index.
+         *
+         * @param slotIndex the slot index.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setSlotIndex(int slotIndex) {
+            this.mSlotIndex = slotIndex;
+            return this;
+        }
+
+        /**
+         * Set the transport type.
+         *
+         * @param transportType the transport type.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setTransportType(@AccessNetworkConstants.TransportType
+                int transportType) {
+            this.mTransportType = transportType;
+            return this;
+        }
+
+        /**
+         * Set the APN type.
+         *
+         * @param apnType  the APN type.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setApnType(@Annotation.ApnType int apnType) {
+            this.mApnType = apnType;
+            return this;
+        }
+
+        /**
+         * Sets the time at which the throttle will expire.  The value is based off of
+         * {@link SystemClock#elapsedRealtime}.
+         *
+         * When setting this value, the throttle type is set to
+         * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}.
+         *
+         * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely.
+         *
+         * @param throttleExpiryTimeMillis The elapsed time at which the throttle expires.
+         *                                 Throws {@link IllegalArgumentException} for values less
+         *                                 than 0.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setThrottleExpiryTimeMillis(
+                @ElapsedRealtimeLong long throttleExpiryTimeMillis) {
+            if (throttleExpiryTimeMillis >= 0) {
+                this.mThrottleExpiryTimeMillis = throttleExpiryTimeMillis;
+                this.mThrottleType = THROTTLE_TYPE_ELAPSED_TIME;
+            } else {
+                throw new IllegalArgumentException("throttleExpiryTimeMillis must be greater than "
+                        + "or equal to 0");
+            }
+            return this;
+        }
+
+        /**
+         * Sets the status of the APN type as not being throttled.
+         *
+         * When setting this value, the throttle type is set to
+         * {@link ApnThrottleStatus#THROTTLE_TYPE_NONE} and the expiry time is set to
+         * {@link Builder#NO_THROTTLE_EXPIRY_TIME}.
+         *
+         * @return The same instance of the builder.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setNoThrottle() {
+            mThrottleType = THROTTLE_TYPE_NONE;
+            mThrottleExpiryTimeMillis = NO_THROTTLE_EXPIRY_TIME;
+            return this;
+        }
+
+        /**
+         * Set the type of request that the framework will make the next time it retries
+         * to call {@link IDataService#setupDataCall}.
+         *
+         * @param retryType the type of request
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setRetryType(@RetryType int retryType) {
+            this.mRetryType = retryType;
+            return this;
+        }
+
+        /**
+         * Build the {@link ApnThrottleStatus}
+         *
+         * @return the {@link ApnThrottleStatus} object
+         */
+        @NonNull
+        public ApnThrottleStatus build() {
+            return new ApnThrottleStatus(
+                    mSlotIndex,
+                    mTransportType,
+                    mApnType,
+                    mThrottleType,
+                    mThrottleExpiryTimeMillis,
+                    mRetryType);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index aae7713..090c970 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -135,6 +135,8 @@
     private final int mMtuV6;
     private final @HandoverFailureMode int mHandoverFailureMode;
     private final int mPduSessionId;
+    private final Qos mDefaultQos;
+    private final List<QosSession> mQosSessions;
 
     /**
      * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error.
@@ -183,6 +185,8 @@
         mMtu = mMtuV4 = mMtuV6 = mtu;
         mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY;
         mPduSessionId = PDU_SESSION_ID_NOT_SET;
+        mDefaultQos = null;
+        mQosSessions = new ArrayList<>();
     }
 
     private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id,
@@ -190,7 +194,8 @@
             @Nullable String interfaceName, @Nullable List<LinkAddress> addresses,
             @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses,
             @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
-            @HandoverFailureMode int handoverFailureMode, int pduSessionId) {
+            @HandoverFailureMode int handoverFailureMode, int pduSessionId,
+            @Nullable Qos defaultQos, @Nullable List<QosSession> qosSessions) {
         mCause = cause;
         mSuggestedRetryTime = suggestedRetryTime;
         mId = id;
@@ -210,6 +215,8 @@
         mMtuV6 = mtuV6;
         mHandoverFailureMode = handoverFailureMode;
         mPduSessionId = pduSessionId;
+        mDefaultQos = defaultQos;
+        mQosSessions = qosSessions;
     }
 
     /** @hide */
@@ -234,6 +241,9 @@
         mMtuV6 = source.readInt();
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
+        mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
+        mQosSessions = new ArrayList<>();
+        source.readList(mQosSessions, QosSession.class.getClassLoader());
     }
 
     /**
@@ -357,6 +367,28 @@
         return mPduSessionId;
     }
 
+    /**
+     * @return default QOS of the data call received from the network
+     *
+     * @hide
+     */
+
+    @Nullable
+    public Qos getDefaultQos() {
+        return mDefaultQos;
+    }
+
+    /**
+     * @return All the dedicated bearer QOS sessions of the data call received from the network
+     *
+     * @hide
+     */
+
+    @NonNull
+    public List<QosSession> getQosSessions() {
+        return mQosSessions;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -377,6 +409,8 @@
            .append(" mtuV6=").append(getMtuV6())
            .append(" handoverFailureMode=").append(getHandoverFailureMode())
            .append(" pduSessionId=").append(getPduSessionId())
+           .append(" defaultQos=").append(mDefaultQos)
+           .append(" qosSessions=").append(mQosSessions)
            .append("}");
         return sb.toString();
     }
@@ -390,12 +424,22 @@
         }
 
         DataCallResponse other = (DataCallResponse) o;
-        return this.mCause == other.mCause
-                && this.mSuggestedRetryTime == other.mSuggestedRetryTime
-                && this.mId == other.mId
-                && this.mLinkStatus == other.mLinkStatus
-                && this.mProtocolType == other.mProtocolType
-                && this.mInterfaceName.equals(other.mInterfaceName)
+
+        final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null) ?
+                mDefaultQos == other.mDefaultQos :
+                mDefaultQos.equals(other.mDefaultQos);
+
+        final boolean isQosSessionsSame = (mQosSessions == null || mQosSessions == null) ?
+                mQosSessions == other.mQosSessions :
+                mQosSessions.size() == other.mQosSessions.size()
+                && mQosSessions.containsAll(other.mQosSessions);
+
+        return mCause == other.mCause
+                && mSuggestedRetryTime == other.mSuggestedRetryTime
+                && mId == other.mId
+                && mLinkStatus == other.mLinkStatus
+                && mProtocolType == other.mProtocolType
+                && mInterfaceName.equals(other.mInterfaceName)
                 && mAddresses.size() == other.mAddresses.size()
                 && mAddresses.containsAll(other.mAddresses)
                 && mDnsAddresses.size() == other.mDnsAddresses.size()
@@ -408,14 +452,17 @@
                 && mMtuV4 == other.mMtuV4
                 && mMtuV6 == other.mMtuV6
                 && mHandoverFailureMode == other.mHandoverFailureMode
-                && mPduSessionId == other.mPduSessionId;
+                && mPduSessionId == other.mPduSessionId
+                && isQosSame
+                && isQosSessionsSame;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
                 mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
-                mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId);
+                mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
+                mQosSessions);
     }
 
     @Override
@@ -440,6 +487,16 @@
         dest.writeInt(mMtuV6);
         dest.writeInt(mHandoverFailureMode);
         dest.writeInt(mPduSessionId);
+        if (mDefaultQos != null) {
+            if (mDefaultQos.getType() == Qos.QOS_TYPE_EPS) {
+                dest.writeParcelable((EpsQos) mDefaultQos, flags);
+            } else {
+                dest.writeParcelable((NrQos) mDefaultQos, flags);
+            }
+        } else {
+            dest.writeParcelable(null, flags);
+        }
+        dest.writeList(mQosSessions);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR =
@@ -519,6 +576,10 @@
 
         private int mPduSessionId = PDU_SESSION_ID_NOT_SET;
 
+        private Qos mDefaultQos;
+
+        private List<QosSession> mQosSessions = new ArrayList<>();
+
         /**
          * Default constructor for Builder.
          */
@@ -713,6 +774,35 @@
         }
 
         /**
+         * Set the default QOS for this data connection.
+         *
+         * @param defaultQos QOS (Quality Of Service) received from network.
+         *
+         * @return The same instance of the builder.
+         *
+         * @hide
+         */
+        public @NonNull Builder setDefaultQos(@Nullable Qos defaultQos) {
+            mDefaultQos = defaultQos;
+            return this;
+        }
+
+        /**
+         * Set the dedicated bearer QOS sessions for this data connection.
+         *
+         * @param qosSessions Dedicated bearer QOS (Quality Of Service) sessions received
+         * from network.
+         *
+         * @return The same instance of the builder.
+         *
+         * @hide
+         */
+        public @NonNull Builder setQosSessions(@NonNull List<QosSession> qosSessions) {
+            mQosSessions = qosSessions;
+            return this;
+        }
+
+        /**
          * Build the DataCallResponse.
          *
          * @return the DataCallResponse object.
@@ -720,7 +810,8 @@
         public @NonNull DataCallResponse build() {
             return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
                     mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
-                    mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId);
+                    mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
+                    mDefaultQos, mQosSessions);
         }
     }
 }
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 7768597..2ec9651 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -107,6 +107,9 @@
     private static final int DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED            = 11;
     private static final int DATA_SERVICE_REQUEST_START_HANDOVER                       = 12;
     private static final int DATA_SERVICE_REQUEST_CANCEL_HANDOVER                      = 13;
+    private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED             = 14;
+    private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED           = 15;
+    private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED                   = 16;
 
     private final HandlerThread mHandlerThread;
 
@@ -129,6 +132,8 @@
 
         private final List<IDataServiceCallback> mDataCallListChangedCallbacks = new ArrayList<>();
 
+        private final List<IDataServiceCallback> mApnUnthrottledCallbacks = new ArrayList<>();
+
         /**
          * Constructor
          * @param slotIndex SIM slot index the data service provider associated with.
@@ -326,6 +331,19 @@
             }
         }
 
+        private void registerForApnUnthrottled(IDataServiceCallback callback) {
+            synchronized (mApnUnthrottledCallbacks) {
+                mApnUnthrottledCallbacks.add(callback);
+            }
+        }
+
+        private void unregisterForApnUnthrottled(IDataServiceCallback callback) {
+            synchronized (mApnUnthrottledCallbacks) {
+                mApnUnthrottledCallbacks.remove(callback);
+            }
+        }
+
+
         /**
          * Notify the system that current data call list changed. Data service must invoke this
          * method whenever there is any data call status changed.
@@ -343,6 +361,21 @@
         }
 
         /**
+         * Notify the system that a given APN was unthrottled.
+         *
+         * @param apn Access Point Name defined by the carrier.
+         */
+        public final void notifyApnUnthrottled(@NonNull String apn) {
+            synchronized (mApnUnthrottledCallbacks) {
+                for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
+                    mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
+                            mSlotIndex, 0, new ApnUnthrottledIndication(apn,
+                                    callback)).sendToTarget();
+                }
+            }
+        }
+
+        /**
          * Called when the instance of data service is destroyed (e.g. got unbind or binder died)
          * or when the data service provider is removed. The extended class should implement this
          * method to perform cleanup works.
@@ -429,6 +462,16 @@
         }
     }
 
+    private static final class ApnUnthrottledIndication {
+        public final String apn;
+        public final IDataServiceCallback callback;
+        ApnUnthrottledIndication(String apn,
+                IDataServiceCallback callback) {
+            this.apn = apn;
+            this.callback = callback;
+        }
+    }
+
     private class DataServiceHandler extends Handler {
 
         DataServiceHandler(Looper looper) {
@@ -544,6 +587,26 @@
                             (cReq.callback != null)
                                     ? new DataServiceCallback(cReq.callback) : null);
                     break;
+                case DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED:
+                    if (serviceProvider == null) break;
+                    serviceProvider.registerForApnUnthrottled((IDataServiceCallback) message.obj);
+                    break;
+                case DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED:
+                    if (serviceProvider == null) break;
+                    callback = (IDataServiceCallback) message.obj;
+                    serviceProvider.unregisterForApnUnthrottled(callback);
+                    break;
+                case DATA_SERVICE_INDICATION_APN_UNTHROTTLED:
+                    if (serviceProvider == null) break;
+                    ApnUnthrottledIndication apnUnthrottledIndication =
+                            (ApnUnthrottledIndication) message.obj;
+                    try {
+                        apnUnthrottledIndication.callback
+                                .onApnUnthrottled(apnUnthrottledIndication.apn);
+                    } catch (RemoteException e) {
+                        loge("Failed to call onApnUnthrottled. " + e);
+                    }
+                    break;
             }
         }
     }
@@ -695,6 +758,26 @@
             mHandler.obtainMessage(DATA_SERVICE_REQUEST_CANCEL_HANDOVER,
                     slotIndex, 0, req).sendToTarget();
         }
+
+        @Override
+        public void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
+            if (callback == null) {
+                loge("registerForUnthrottleApn: callback is null");
+                return;
+            }
+            mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED, slotIndex,
+                    0, callback).sendToTarget();
+        }
+
+        @Override
+        public void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
+            if (callback == null) {
+                loge("uregisterForUnthrottleApn: callback is null");
+                return;
+            }
+            mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED,
+                    slotIndex, 0, callback).sendToTarget();
+        }
     }
 
     private void log(String s) {
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index eef0e01..52bf15f 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -233,7 +233,7 @@
      */
     @NonNull
     public static String resultCodeToString(@DataServiceCallback.ResultCode int resultCode) {
-        switch(resultCode) {
+        switch (resultCode) {
             case RESULT_SUCCESS:
                 return "RESULT_SUCCESS";
             case RESULT_ERROR_UNSUPPORTED:
@@ -248,4 +248,22 @@
                 return "Missing case for result code=" + resultCode;
         }
     }
+
+    /**
+     * Indicates that the specified APN is no longer throttled.
+     *
+     * @param apn Access Point Name defined by the carrier.
+     */
+    public void onApnUnthrottled(@NonNull String apn) {
+        if (mCallback != null) {
+            try {
+                if (DBG) Rlog.d(TAG, "onApnUnthrottled");
+                mCallback.onApnUnthrottled(apn);
+            } catch (RemoteException e) {
+                Rlog.e(TAG, "onApnUnthrottled: remote exception", e);
+            }
+        } else {
+            Rlog.e(TAG, "onApnUnthrottled: callback is null!");
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/data/EpsQos.java b/telephony/java/android/telephony/data/EpsQos.java
new file mode 100644
index 0000000..ad43068
--- /dev/null
+++ b/telephony/java/android/telephony/data/EpsQos.java
@@ -0,0 +1,105 @@
+/**
+ * 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.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+
+/**
+ * Class that stores information specific to NR QOS.
+ *
+ * @hide
+ */
+public final class EpsQos extends Qos implements Parcelable {
+
+    int qosClassId;
+
+    public EpsQos() {
+        super(Qos.QOS_TYPE_EPS,
+                new android.hardware.radio.V1_6.QosBandwidth(),
+                new android.hardware.radio.V1_6.QosBandwidth());
+    }
+
+    public EpsQos(@NonNull android.hardware.radio.V1_6.EpsQos qos) {
+        super(Qos.QOS_TYPE_EPS, qos.downlink, qos.uplink);
+        qosClassId = qos.qci;
+    }
+
+    private EpsQos(Parcel source) {
+        super(source);
+        qosClassId = source.readInt();
+    }
+
+    public static @NonNull EpsQos createFromParcelBody(@NonNull Parcel in) {
+        return new EpsQos(in);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(Qos.QOS_TYPE_EPS, dest, flags);
+        dest.writeInt(qosClassId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), qosClassId);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || !(o instanceof EpsQos)) {
+            return false;
+        }
+
+        EpsQos other = (EpsQos) o;
+
+        return this.qosClassId == other.qosClassId
+               && super.equals(other);
+    }
+
+    @Override
+    public String toString() {
+        return "EpsQos {"
+                + " qosClassId=" + qosClassId
+                + " downlink=" + downlink
+                + " uplink=" + uplink + "}";
+    }
+
+    public static final @NonNull Parcelable.Creator<EpsQos> CREATOR =
+            new Parcelable.Creator<EpsQos>() {
+                @Override
+                public EpsQos createFromParcel(Parcel source) {
+                    return new EpsQos(source);
+                }
+
+                @Override
+                public EpsQos[] newArray(int size) {
+                    return new EpsQos[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 33226fe..3f1f033 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -40,4 +40,6 @@
     void unregisterForDataCallListChanged(int slotId, IDataServiceCallback callback);
     void startHandover(int slotId, int cid, IDataServiceCallback callback);
     void cancelHandover(int slotId, int cid, IDataServiceCallback callback);
+    void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
+    void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
 }
diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
index d296e7b..9cc2fea 100644
--- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
@@ -32,4 +32,5 @@
     void onDataCallListChanged(in List<DataCallResponse> dataCallList);
     void onHandoverStarted(int result);
     void onHandoverCancelled(int result);
+    void onApnUnthrottled(in String apn);
 }
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
index 3bf09bc..2904082 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -17,6 +17,7 @@
 package android.telephony.data;
 
 import android.telephony.data.IQualifiedNetworksServiceCallback;
+import android.telephony.data.ApnThrottleStatus;
 
 /**
  * {@hide}
@@ -25,4 +26,5 @@
 {
     oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback);
     oneway void removeNetworkAvailabilityProvider(int slotId);
+    oneway void reportApnThrottleStatusChanged(int slotId, in List<ApnThrottleStatus> statuses);
 }
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
new file mode 100644
index 0000000..2011eed
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -0,0 +1,112 @@
+/**
+ * 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.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Class that stores information specific to NR QOS.
+ *
+ * @hide
+ */
+public final class NrQos extends Qos implements Parcelable {
+    int qosFlowId;
+    int fiveQi;
+    int averagingWindowMs;
+
+    public NrQos(@NonNull android.hardware.radio.V1_6.NrQos qos) {
+        super(Qos.QOS_TYPE_NR, qos.downlink, qos.uplink);
+        fiveQi = qos.fiveQi;
+        qosFlowId = qos.qfi;
+        averagingWindowMs = qos.averagingWindowMs;
+    }
+
+    private NrQos(Parcel source) {
+        super(source);
+        this.qosFlowId = source.readInt();
+        this.fiveQi = source.readInt();
+        this.averagingWindowMs = source.readInt();
+    }
+
+    public static @NonNull NrQos createFromParcelBody(@NonNull Parcel in) {
+        return new NrQos(in);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags);
+        dest.writeInt(qosFlowId);
+        dest.writeInt(fiveQi);
+        dest.writeInt(averagingWindowMs);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), qosFlowId, fiveQi, averagingWindowMs);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || !(o instanceof NrQos)) {
+            return false;
+        }
+
+        NrQos other = (NrQos) o;
+
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        return this.qosFlowId == other.qosFlowId
+            && this.fiveQi == other.fiveQi
+            && this.averagingWindowMs == other.averagingWindowMs;
+    }
+
+    @Override
+    public String toString() {
+        return "NrQos {"
+                + " fiveQi=" + fiveQi
+                + " downlink=" + downlink
+                + " uplink=" + uplink
+                + " qosFlowId=" + qosFlowId
+                + " averagingWindowMs=" + averagingWindowMs + "}";
+    }
+
+    public static final @NonNull Parcelable.Creator<NrQos> CREATOR =
+            new Parcelable.Creator<NrQos>() {
+                @Override
+                public NrQos createFromParcel(Parcel source) {
+                    return new NrQos(source);
+                }
+
+                @Override
+                public NrQos[] newArray(int size) {
+                    return new NrQos[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
new file mode 100644
index 0000000..c8bb91e
--- /dev/null
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -0,0 +1,175 @@
+/**
+ * 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.telephony.data;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class that stores information specific to QOS.
+ *
+ * @hide
+ */
+public abstract class Qos {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "QOS_TYPE_",
+            value = {QOS_TYPE_EPS, QOS_TYPE_NR})
+    public @interface QosType {}
+
+    @QosType
+    final int type;
+
+    static final int QOS_TYPE_EPS = 1;
+    static final int QOS_TYPE_NR = 2;
+
+    final QosBandwidth downlink;
+    final QosBandwidth uplink;
+
+    Qos(int type,
+            @NonNull android.hardware.radio.V1_6.QosBandwidth downlink,
+            @NonNull android.hardware.radio.V1_6.QosBandwidth uplink) {
+        this.type = type;
+        this.downlink = new QosBandwidth(downlink.maxBitrateKbps, downlink.guaranteedBitrateKbps);
+        this.uplink = new QosBandwidth(uplink.maxBitrateKbps, uplink.guaranteedBitrateKbps);
+    }
+
+    static class QosBandwidth implements Parcelable {
+        int maxBitrateKbps;
+        int guaranteedBitrateKbps;
+
+        QosBandwidth() {
+        }
+
+        QosBandwidth(int maxBitrateKbps, int guaranteedBitrateKbps) {
+            this.maxBitrateKbps = maxBitrateKbps;
+            this.guaranteedBitrateKbps = guaranteedBitrateKbps;
+        }
+
+        private QosBandwidth(Parcel source) {
+            maxBitrateKbps = source.readInt();
+            guaranteedBitrateKbps = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(maxBitrateKbps);
+            dest.writeInt(guaranteedBitrateKbps);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(maxBitrateKbps, guaranteedBitrateKbps);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+
+            if (o == null || !(o instanceof QosBandwidth)) {
+                return false;
+            }
+
+            QosBandwidth other = (QosBandwidth) o;
+            return maxBitrateKbps == other.maxBitrateKbps
+                    && guaranteedBitrateKbps == other.guaranteedBitrateKbps;
+        }
+
+        @Override
+        public String toString() {
+            return "Bandwidth {"
+                    + " maxBitrateKbps=" + maxBitrateKbps
+                    + " guaranteedBitrateKbps=" + guaranteedBitrateKbps + "}";
+        }
+
+        public static final @NonNull Parcelable.Creator<QosBandwidth> CREATOR =
+                new Parcelable.Creator<QosBandwidth>() {
+                    @Override
+                    public QosBandwidth createFromParcel(Parcel source) {
+                        return new QosBandwidth(source);
+                    }
+
+                    @Override
+                    public QosBandwidth[] newArray(int size) {
+                        return new QosBandwidth[size];
+                    }
+                };
+    };
+
+    protected Qos(@NonNull Parcel source) {
+        type = source.readInt();
+        downlink = source.readParcelable(QosBandwidth.class.getClassLoader());
+        uplink = source.readParcelable(QosBandwidth.class.getClassLoader());
+    }
+
+    /**
+     * Used by child classes for parceling.
+     *
+     * @hide
+     */
+    @CallSuper
+    public void writeToParcel(@QosType int type, Parcel dest, int flags) {
+        dest.writeInt(type);
+        dest.writeParcelable(downlink, flags);
+        dest.writeParcelable(uplink, flags);
+    }
+
+    /** @hide */
+    public static @NonNull Qos create(@NonNull android.hardware.radio.V1_6.Qos qos) {
+        switch (qos.getDiscriminator()) {
+            case android.hardware.radio.V1_6.Qos.hidl_discriminator.eps:
+                  return new EpsQos(qos.eps());
+            case android.hardware.radio.V1_6.Qos.hidl_discriminator.nr:
+                  return new NrQos(qos.nr());
+            default:
+                  return null;
+        }
+    }
+
+    /** @hide */
+    public @QosType int getType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(downlink, uplink);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        Qos other = (Qos) o;
+        return type == other.type
+                && downlink.equals(other.downlink)
+                && uplink.equals(other.uplink);
+    }
+}
diff --git a/telephony/java/android/telephony/data/QosFilter.java b/telephony/java/android/telephony/data/QosFilter.java
new file mode 100644
index 0000000..6927744
--- /dev/null
+++ b/telephony/java/android/telephony/data/QosFilter.java
@@ -0,0 +1,373 @@
+/**
+ * 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.telephony.data;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * Class that stores QOS filter parameters as defined in
+ * 3gpp 24.008 10.5.6.12 and 3gpp 24.501 9.11.4.13.
+ *
+ * @hide
+ */
+public final class QosFilter implements Parcelable {
+
+    private List<LinkAddress> localAddresses;
+    private List<LinkAddress> remoteAddresses;
+    private PortRange localPort;
+    private PortRange remotePort;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "QOS_PROTOCOL_",
+            value = {QOS_PROTOCOL_UNSPECIFIED, QOS_PROTOCOL_TCP, QOS_PROTOCOL_UDP,
+                    QOS_PROTOCOL_ESP, QOS_PROTOCOL_AH})
+    public @interface QosProtocol {}
+
+    public static final int QOS_PROTOCOL_UNSPECIFIED =
+            android.hardware.radio.V1_6.QosProtocol.UNSPECIFIED;
+    public static final int QOS_PROTOCOL_TCP = android.hardware.radio.V1_6.QosProtocol.TCP;
+    public static final int QOS_PROTOCOL_UDP = android.hardware.radio.V1_6.QosProtocol.UDP;
+    public static final int QOS_PROTOCOL_ESP = android.hardware.radio.V1_6.QosProtocol.ESP;
+    public static final int QOS_PROTOCOL_AH = android.hardware.radio.V1_6.QosProtocol.AH;
+
+    @QosProtocol
+    private int protocol;
+
+    private int typeOfServiceMask;
+
+    private long flowLabel;
+
+    /** IPSec security parameter index */
+    private long securityParameterIndex;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "QOS_FILTER_DIRECTION_",
+            value = {QOS_FILTER_DIRECTION_DOWNLINK, QOS_FILTER_DIRECTION_UPLINK,
+                    QOS_FILTER_DIRECTION_BIDIRECTIONAL})
+    public @interface QosFilterDirection {}
+
+    public static final int QOS_FILTER_DIRECTION_DOWNLINK =
+            android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK;
+    public static final int QOS_FILTER_DIRECTION_UPLINK =
+            android.hardware.radio.V1_6.QosFilterDirection.UPLINK;
+    public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL =
+            android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL;
+
+    @QosFilterDirection
+    private int filterDirection;
+
+    /**
+     * Specified the order in which the filter needs to be matched.
+     * A Lower numerical value has a higher precedence.
+     */
+    private int precedence;
+
+    QosFilter() {
+        localAddresses = new ArrayList<>();
+        remoteAddresses = new ArrayList<>();
+        localPort = new PortRange();
+        remotePort = new PortRange();
+        protocol = QOS_PROTOCOL_UNSPECIFIED;
+        filterDirection = QOS_FILTER_DIRECTION_BIDIRECTIONAL;
+    }
+
+    public QosFilter(List<LinkAddress> localAddresses, List<LinkAddress> remoteAddresses,
+            PortRange localPort, PortRange remotePort, int protocol, int tos,
+            long flowLabel, long spi, int direction, int precedence) {
+        this.localAddresses = localAddresses;
+        this.remoteAddresses = remoteAddresses;
+        this.localPort = localPort;
+        this.remotePort = remotePort;
+        this.protocol = protocol;
+        this.typeOfServiceMask = tos;
+        this.flowLabel = flowLabel;
+        this.securityParameterIndex = spi;
+        this.filterDirection = direction;
+        this.precedence = precedence;
+    }
+
+    /** @hide */
+    public static @NonNull QosFilter create(
+            @NonNull android.hardware.radio.V1_6.QosFilter qosFilter) {
+        QosFilter ret = new QosFilter();
+
+        String[] localAddresses = qosFilter.localAddresses.stream().toArray(String[]::new);
+        if (localAddresses != null) {
+            for (String address : localAddresses) {
+                ret.localAddresses.add(createLinkAddressFromString(address));
+            }
+        }
+
+        String[] remoteAddresses = qosFilter.remoteAddresses.stream().toArray(String[]::new);
+        if (remoteAddresses != null) {
+            for (String address : remoteAddresses) {
+                ret.remoteAddresses.add(createLinkAddressFromString(address));
+            }
+        }
+
+        if (qosFilter.localPort != null) {
+            if (qosFilter.localPort.getDiscriminator()
+                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+                final android.hardware.radio.V1_6.PortRange portRange = qosFilter.localPort.range();
+                ret.localPort.start = portRange.start;
+                ret.localPort.end = portRange.end;
+            }
+        }
+
+        if (qosFilter.remotePort != null) {
+            if (qosFilter.remotePort.getDiscriminator()
+                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+                final android.hardware.radio.V1_6.PortRange portRange
+                        = qosFilter.remotePort.range();
+                ret.remotePort.start = portRange.start;
+                ret.remotePort.end = portRange.end;
+            }
+        }
+
+        ret.protocol = qosFilter.protocol;
+
+        if (qosFilter.tos != null) {
+            if (qosFilter.tos.getDiscriminator()
+                == android.hardware.radio.V1_6.QosFilter.TypeOfService.hidl_discriminator.value) {
+                ret.typeOfServiceMask = qosFilter.tos.value();
+            }
+        }
+
+        if (qosFilter.flowLabel != null) {
+            if (qosFilter.flowLabel.getDiscriminator()
+                == android.hardware.radio.V1_6.QosFilter.Ipv6FlowLabel.hidl_discriminator.value) {
+                ret.flowLabel = qosFilter.flowLabel.value();
+            }
+        }
+
+        if (qosFilter.spi != null) {
+            if (qosFilter.spi.getDiscriminator()
+                == android.hardware.radio.V1_6.QosFilter.IpsecSpi.hidl_discriminator.value) {
+                ret.securityParameterIndex = qosFilter.spi.value();
+            }
+        }
+
+        ret.filterDirection = qosFilter.direction;
+        ret.precedence = qosFilter.precedence;
+
+        return ret;
+    }
+
+    public static class PortRange implements Parcelable {
+        int start;
+        int end;
+
+        PortRange() {
+            start = -1;
+            end = -1;
+        }
+
+        private PortRange(Parcel source) {
+            start = source.readInt();
+            end = source.readInt();
+        }
+
+        public PortRange(int start, int end) {
+            this.start = start;
+            this.end = end;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(start);
+            dest.writeInt(end);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @NonNull Parcelable.Creator<PortRange> CREATOR =
+                new Parcelable.Creator<PortRange>() {
+                    @Override
+                    public PortRange createFromParcel(Parcel source) {
+                        return new PortRange(source);
+                    }
+
+                    @Override
+                    public PortRange[] newArray(int size) {
+                        return new PortRange[size];
+                    }
+                };
+
+        @Override
+        public String toString() {
+            return "PortRange {"
+                    + " start=" + start
+                    + " end=" + end + "}";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+
+            if (o == null || !(o instanceof PortRange)) {
+              return false;
+            }
+
+            PortRange other = (PortRange) o;
+            return start == other.start
+                    && end == other.end;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(start, end);
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "QosFilter {"
+                + " localAddresses=" + localAddresses
+                + " remoteAddresses=" + remoteAddresses
+                + " localPort=" + localPort
+                + " remotePort=" + remotePort
+                + " protocol=" + protocol
+                + " typeOfServiceMask=" + typeOfServiceMask
+                + " flowLabel=" + flowLabel
+                + " securityParameterIndex=" + securityParameterIndex
+                + " filterDirection=" + filterDirection
+                + " precedence=" + precedence + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(localAddresses, remoteAddresses, localPort,
+                remotePort, protocol, typeOfServiceMask, flowLabel,
+                securityParameterIndex, filterDirection, precedence);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || !(o instanceof QosFilter)) {
+            return false;
+        }
+
+        QosFilter other = (QosFilter) o;
+
+        return localAddresses.size() == other.localAddresses.size()
+                && localAddresses.containsAll(other.localAddresses)
+                && remoteAddresses.size() == other.remoteAddresses.size()
+                && remoteAddresses.containsAll(other.remoteAddresses)
+                && localPort.equals(other.localPort)
+                && remotePort.equals(other.remotePort)
+                && protocol == other.protocol
+                && typeOfServiceMask == other.typeOfServiceMask
+                && flowLabel == other.flowLabel
+                && securityParameterIndex == other.securityParameterIndex
+                && filterDirection == other.filterDirection
+                && precedence == other.precedence;
+    }
+
+    private static LinkAddress createLinkAddressFromString(String addressString) {
+        addressString = addressString.trim();
+        InetAddress address = null;
+        int prefixLength = -1;
+        try {
+            String[] pieces = addressString.split("/", 2);
+            address = InetAddresses.parseNumericAddress(pieces[0]);
+            if (pieces.length == 1) {
+                prefixLength = (address instanceof Inet4Address) ? 32 : 128;
+            } else if (pieces.length == 2) {
+                prefixLength = Integer.parseInt(pieces[1]);
+            }
+        } catch (NullPointerException e) {            // Null string.
+        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
+        } catch (NumberFormatException e) {           // Non-numeric prefix.
+        } catch (IllegalArgumentException e) {        // Invalid IP address.
+        }
+
+        if (address == null || prefixLength == -1) {
+            throw new IllegalArgumentException("Invalid link address " + addressString);
+        }
+
+        return new LinkAddress(address, prefixLength, 0, 0,
+                LinkAddress.LIFETIME_UNKNOWN, LinkAddress.LIFETIME_UNKNOWN);
+    }
+
+    private QosFilter(Parcel source) {
+        localAddresses = new ArrayList<>();
+        source.readList(localAddresses, LinkAddress.class.getClassLoader());
+        remoteAddresses = new ArrayList<>();
+        source.readList(remoteAddresses, LinkAddress.class.getClassLoader());
+        localPort = source.readParcelable(PortRange.class.getClassLoader());
+        remotePort = source.readParcelable(PortRange.class.getClassLoader());
+        protocol = source.readInt();
+        typeOfServiceMask = source.readInt();
+        flowLabel = source.readLong();
+        securityParameterIndex = source.readLong();
+        filterDirection = source.readInt();
+        precedence = source.readInt();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeList(localAddresses);
+        dest.writeList(remoteAddresses);
+        dest.writeParcelable(localPort, flags);
+        dest.writeParcelable(remotePort, flags);
+        dest.writeInt(protocol);
+        dest.writeInt(typeOfServiceMask);
+        dest.writeLong(flowLabel);
+        dest.writeLong(securityParameterIndex);
+        dest.writeInt(filterDirection);
+        dest.writeInt(precedence);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<QosFilter> CREATOR =
+            new Parcelable.Creator<QosFilter>() {
+                @Override
+                public QosFilter createFromParcel(Parcel source) {
+                    return new QosFilter(source);
+                }
+
+                @Override
+                public QosFilter[] newArray(int size) {
+                    return new QosFilter[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/data/QosSession.java b/telephony/java/android/telephony/data/QosSession.java
new file mode 100644
index 0000000..f07b6a9
--- /dev/null
+++ b/telephony/java/android/telephony/data/QosSession.java
@@ -0,0 +1,125 @@
+/**
+ * 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.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * Class that stores information specific to QOS session.
+ *
+ * @hide
+ */
+public final class QosSession implements Parcelable{
+
+    final int qosSessionId;
+    final Qos qos;
+    final List<QosFilter> qosFilterList;
+
+    public QosSession(int qosSessionId, @NonNull Qos qos, @NonNull List<QosFilter> qosFilterList) {
+        this.qosSessionId = qosSessionId;
+        this.qos = qos;
+        this.qosFilterList = qosFilterList;
+    }
+
+    private QosSession(Parcel source) {
+        qosSessionId = source.readInt();
+        qos = source.readParcelable(Qos.class.getClassLoader());
+        qosFilterList = new ArrayList<>();
+        source.readList(qosFilterList, QosFilter.class.getClassLoader());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(qosSessionId);
+        if (qos.getType() == Qos.QOS_TYPE_EPS) {
+            dest.writeParcelable((EpsQos)qos, flags);
+        } else {
+            dest.writeParcelable((NrQos)qos, flags);
+        }
+        dest.writeList(qosFilterList);
+    }
+
+    public static @NonNull QosSession create(
+            @NonNull android.hardware.radio.V1_6.QosSession qosSession) {
+        List<QosFilter> qosFilters = new ArrayList<>();
+
+        if (qosSession.qosFilters != null) {
+            for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
+                qosFilters.add(QosFilter.create(filter));
+            }
+        }
+
+        return new QosSession(
+                        qosSession.qosSessionId,
+                        Qos.create(qosSession.qos),
+                        qosFilters);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "QosSession {"
+                + " qosSessionId=" + qosSessionId
+                + " qos=" + qos
+                + " qosFilterList=" + qosFilterList + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(qosSessionId, qos, qosFilterList);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || !(o instanceof QosSession)) {
+            return false;
+        }
+
+        QosSession other = (QosSession) o;
+        return this.qosSessionId == other.qosSessionId
+                && this.qos.equals(other.qos)
+                && this.qosFilterList.size() == other.qosFilterList.size()
+                && this.qosFilterList.containsAll(other.qosFilterList);
+    }
+
+
+    public static final @NonNull Parcelable.Creator<QosSession> CREATOR =
+            new Parcelable.Creator<QosSession>() {
+                @Override
+                public QosSession createFromParcel(Parcel source) {
+                    return new QosSession(source);
+                }
+
+                @Override
+                public QosSession[] newArray(int size) {
+                    return new QosSession[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 05971c4..4af63b4 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.Annotation.ApnType;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,6 +66,7 @@
     private static final int QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER               = 2;
     private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS          = 3;
     private static final int QNS_UPDATE_QUALIFIED_NETWORKS                          = 4;
+    private static final int QNS_APN_THROTTLE_STATUS_CHANGED                        = 5;
 
     private final HandlerThread mHandlerThread;
 
@@ -160,6 +162,17 @@
         }
 
         /**
+         * The framework calls this method when the throttle status of an APN changes.
+         *
+         * This method is meant to be overridden.
+         *
+         * @param statuses the statuses that have changed
+         */
+        public void reportApnThrottleStatusChanged(@NonNull List<ApnThrottleStatus> statuses) {
+            Log.d(TAG, "reportApnThrottleStatusChanged: statuses size=" + statuses.size());
+        }
+
+        /**
          * Called when the qualified networks provider is removed. The extended class should
          * implement this method to perform cleanup works.
          */
@@ -197,6 +210,12 @@
                                 + slotIndex);
                     }
                     break;
+                case QNS_APN_THROTTLE_STATUS_CHANGED:
+                    if (provider != null) {
+                        List<ApnThrottleStatus> statuses = (List<ApnThrottleStatus>) message.obj;
+                        provider.reportApnThrottleStatusChanged(statuses);
+                    }
+                    break;
 
                 case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER:
                     if (provider != null) {
@@ -286,6 +305,13 @@
             mHandler.obtainMessage(QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER, slotIndex, 0)
                     .sendToTarget();
         }
+
+        @Override
+        public void reportApnThrottleStatusChanged(int slotIndex,
+                List<ApnThrottleStatus> statuses) {
+            mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses)
+                    .sendToTarget();
+        }
     }
 
     private void log(String s) {
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 76fc4f7..6fbde50 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
     public static final int EVENT_NR_TIMER_WATCHDOG = BASE + 53;
     public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54;
     public static final int EVENT_SIM_STATE_UPDATED = BASE + 55;
+    public static final int EVENT_APN_UNTHROTTLED = BASE + 56;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0bd4851..3e7defb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2154,6 +2154,8 @@
     oneway void setSystemSelectionChannels(in List<RadioAccessSpecifier> specifiers,
             int subId, IBooleanConsumer resultCallback);
 
+    List<RadioAccessSpecifier> getSystemSelectionChannels(int subId);
+
     boolean isMvnoMatched(int subId, int mvnoType, String mvnoMatchData);
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9d4072f..a2361a7 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -519,6 +519,7 @@
     int RIL_REQUEST_RELEASE_PDU_SESSION_ID = 216;
     int RIL_REQUEST_START_HANDOVER = 217;
     int RIL_REQUEST_CANCEL_HANDOVER = 218;
+    int RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS = 219;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/BatteryStatsPerfTest/Android.bp b/tests/BatteryStatsPerfTest/Android.bp
new file mode 100644
index 0000000..58ccec7
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "BatteryStatsPerfTests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "apct-perftests-utils",
+        "truth-prebuilt",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/BatteryStatsPerfTest/AndroidManifest.xml b/tests/BatteryStatsPerfTest/AndroidManifest.xml
new file mode 100644
index 0000000..7633d52
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.perftests.batterystats">
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.BATTERY_STATS"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.frameworks.perftests.batterystats"/>
+</manifest>
diff --git a/tests/BatteryStatsPerfTest/AndroidTest.xml b/tests/BatteryStatsPerfTest/AndroidTest.xml
new file mode 100644
index 0000000..2f9e114
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs BatteryStats service Performance Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="BatteryStatsPerfTests.apk"/>
+        <option name="cleanup-apks" value="true"/>
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="BatteryStatsPerfTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.perftests.batterystats"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
new file mode 100644
index 0000000..6266cda
--- /dev/null
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BatteryStatsHelperPerfTest {
+
+    @Rule
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    /**
+     * Measures the performance of {@link BatteryStatsHelper#getStats()}, which triggers
+     * a battery stats sync on every iteration.
+     */
+    @Test
+    public void testGetStats_forceUpdate() {
+        final Context context = InstrumentationRegistry.getContext();
+        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+                true /* collectBatteryBroadcast */);
+        statsHelper.create((Bundle) null);
+        statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            statsHelper.clearStats();
+            state.resumeTiming();
+
+            statsHelper.getStats();
+
+            assertThat(statsHelper.getUsageList()).isNotEmpty();
+        }
+    }
+
+    /**
+     * Measures performance of the {@link BatteryStatsHelper#getStats(boolean)}, which does
+     * not trigger a sync and just returns current values.
+     */
+    @Test
+    public void testGetStats_cached() {
+        final Context context = InstrumentationRegistry.getContext();
+        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+                true /* collectBatteryBroadcast */);
+        statsHelper.create((Bundle) null);
+        statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            statsHelper.clearStats();
+            state.resumeTiming();
+
+            statsHelper.getStats(false /* forceUpdate */);
+
+            assertThat(statsHelper.getUsageList()).isNotEmpty();
+        }
+    }
+
+    @Test
+    public void testPowerCalculation() {
+        final Context context = InstrumentationRegistry.getContext();
+        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+                true /* collectBatteryBroadcast */);
+        statsHelper.create((Bundle) null);
+        statsHelper.getStats();
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            // This will use the cached BatteryStatsObject
+            statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+            assertThat(statsHelper.getUsageList()).isNotEmpty();
+        }
+    }
+
+    @Test
+    public void testEndToEnd() {
+        final Context context = InstrumentationRegistry.getContext();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
+                    true /* collectBatteryBroadcast */);
+            statsHelper.create((Bundle) null);
+            statsHelper.clearStats();
+            statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
+
+            state.pauseTiming();
+
+            List<BatterySipper> usageList = statsHelper.getUsageList();
+            double power = 0;
+            for (int i = 0; i < usageList.size(); i++) {
+                BatterySipper sipper = usageList.get(i);
+                power += sipper.sumPower();
+            }
+
+            assertThat(power).isGreaterThan(0.0);
+
+            state.resumeTiming();
+        }
+    }
+}
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 98e02ce..58f56f2 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -27,6 +27,8 @@
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
     <!-- Enable / Disable tracing !-->
     <uses-permission android:name="android.permission.DUMP" />
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
     <!-- Run layers trace -->
     <uses-permission android:name="android.permission.HARDWARE_TEST"/>
     <!-- Workaround grant runtime permission exception from b/152733071 -->
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
index 647da2a..48031de 100644
--- a/tests/SurfaceViewBufferTests/Android.bp
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -33,6 +33,8 @@
         "kotlinx-coroutines-android",
         "flickerlib",
         "truth-prebuilt",
+        "cts-wm-util",
+        "CtsSurfaceValidatorLib",
     ],
 }
 
@@ -43,6 +45,7 @@
     ],
     shared_libs: [
         "libutils",
+        "libui",
         "libgui",
         "liblog",
         "libandroid",
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index 95885c1..c910ecd 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -23,12 +23,19 @@
     <uses-permission android:name="android.permission.DUMP" />
     <!-- Enable / Disable sv blast adapter !-->
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Readback virtual display output !-->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <!-- Save failed test bitmap images !-->
+    <uses-permission android:name="android.Manifest.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application android:allowBackup="false"
          android:supportsRtl="true">
         <activity android:name=".MainActivity"
                   android:taskAffinity="com.android.test.MainActivity"
                   android:theme="@style/AppTheme"
+                  android:configChanges="orientation|screenSize"
                   android:label="SurfaceViewBufferTestApp"
                   android:exported="true">
             <intent-filter>
@@ -36,6 +43,10 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
+                 android:foregroundServiceType="mediaProjection"
+                 android:enabled="true">
+        </service>
         <uses-library android:name="android.test.runner"/>
     </application>
 
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index 0c86524..ce226fd 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -32,6 +32,7 @@
 extern "C" {
 int i = 0;
 static ANativeWindow* sAnw;
+static std::map<uint32_t /* slot */, ANativeWindowBuffer*> sBuffers;
 
 JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass,
                                                                      jobject surfaceObject) {
@@ -39,11 +40,14 @@
     assert(sAnw);
     android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
     surface->enableFrameTimestamps(true);
+    surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false);
+    native_window_set_usage(sAnw, GRALLOC_USAGE_SW_WRITE_OFTEN);
+    native_window_set_buffers_format(sAnw, HAL_PIXEL_FORMAT_RGBA_8888);
     return 0;
 }
 
 JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed(
-        JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) {
+        JNIEnv*, jclass, jlong jFrameNumber, jint timeoutMs) {
     using namespace std::chrono_literals;
     assert(sAnw);
     android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
@@ -63,8 +67,8 @@
                                     &outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime);
         if (outDisplayPresentTime < 0) {
             auto end = std::chrono::steady_clock::now();
-            if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() >
-                timeoutSec) {
+            if (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() >
+                timeoutMs) {
                 return -1;
             }
         }
@@ -99,4 +103,121 @@
     assert(sAnw);
     return ANativeWindow_setBuffersGeometry(sAnw, w, h, format);
 }
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersTransform(
+        JNIEnv* /* env */, jclass /* clazz */, jint transform) {
+    assert(sAnw);
+    return native_window_set_buffers_transform(sAnw, transform);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetScalingMode(JNIEnv* /* env */,
+                                                                                jclass /* clazz */,
+                                                                                jint scalingMode) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setScalingMode(scalingMode);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceDequeueBuffer(JNIEnv* /* env */,
+                                                                               jclass /* clazz */,
+                                                                               jint slot,
+                                                                               jint timeoutMs) {
+    assert(sAnw);
+    ANativeWindowBuffer* anb;
+    int fenceFd;
+    int result = sAnw->dequeueBuffer(sAnw, &anb, &fenceFd);
+    if (result != android::OK) {
+        return result;
+    }
+    sBuffers[slot] = anb;
+    android::sp<android::Fence> fence(new android::Fence(fenceFd));
+    int waitResult = fence->wait(timeoutMs);
+    if (waitResult != android::OK) {
+        sAnw->cancelBuffer(sAnw, sBuffers[slot], -1);
+        sBuffers[slot] = nullptr;
+        return waitResult;
+    }
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceCancelBuffer(JNIEnv* /* env */,
+                                                                              jclass /* clazz */,
+                                                                              jint slot) {
+    assert(sAnw);
+    assert(sBuffers[slot]);
+    int result = sAnw->cancelBuffer(sAnw, sBuffers[slot], -1);
+    sBuffers[slot] = nullptr;
+    return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_drawBuffer(JNIEnv* env,
+                                                                     jclass /* clazz */, jint slot,
+                                                                     jintArray jintArrayColor) {
+    assert(sAnw);
+    assert(sBuffers[slot]);
+
+    int* color = env->GetIntArrayElements(jintArrayColor, nullptr);
+
+    ANativeWindowBuffer* buffer = sBuffers[slot];
+    android::sp<android::GraphicBuffer> graphicBuffer(static_cast<android::GraphicBuffer*>(buffer));
+    const android::Rect bounds(buffer->width, buffer->height);
+    android::Region newDirtyRegion;
+    newDirtyRegion.set(bounds);
+
+    void* vaddr;
+    int fenceFd = -1;
+    graphicBuffer->lockAsync(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                             newDirtyRegion.bounds(), &vaddr, fenceFd);
+
+    for (int32_t row = 0; row < buffer->height; row++) {
+        uint8_t* dst = static_cast<uint8_t*>(vaddr) + (buffer->stride * row) * 4;
+        for (int32_t column = 0; column < buffer->width; column++) {
+            dst[0] = color[0];
+            dst[1] = color[1];
+            dst[2] = color[2];
+            dst[3] = color[3];
+            dst += 4;
+        }
+    }
+    graphicBuffer->unlockAsync(&fenceFd);
+    env->ReleaseIntArrayElements(jintArrayColor, color, JNI_ABORT);
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceQueueBuffer(JNIEnv* /* env */,
+                                                                             jclass /* clazz */,
+                                                                             jint slot,
+                                                                             jboolean freeSlot) {
+    assert(sAnw);
+    assert(sBuffers[slot]);
+    int result = sAnw->queueBuffer(sAnw, sBuffers[slot], -1);
+    if (freeSlot) {
+        sBuffers[slot] = nullptr;
+    }
+    return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
+        JNIEnv* /* env */, jclass /* clazz */, jint count) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    int result = native_window_set_buffer_count(sAnw, count);
+    return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetSharedBufferMode(
+        JNIEnv* /* env */, jclass /* clazz */, jboolean shared) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    int result = native_window_set_shared_buffer_mode(sAnw, shared);
+    return result;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetAutoRefresh(
+        JNIEnv* /* env */, jclass /* clazz */, jboolean autoRefresh) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    int result = native_window_set_auto_refresh(sAnw, autoRefresh);
+    return result;
+}
 }
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
new file mode 100644
index 0000000..eb16bad
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+    /** Submit buffers as fast as possible and make sure they are presented on display */
+    @Test
+    fun testQueueBuffers() {
+        val numFrames = 100L
+        val trace = withTrace {
+            for (i in 1..numFrames) {
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+            }
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 1000 /* ms */))
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+    }
+
+    @Test
+    fun testSetBufferScalingMode_outOfOrderQueueBuffer() {
+        val trace = withTrace {
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+
+            it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(2, 5000 /* ms */))
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", 1..2L)
+    }
+
+    @Test
+    fun testSetBufferScalingMode_multipleDequeueBuffer() {
+        val numFrames = 20L
+        val trace = withTrace {
+            for (count in 1..(numFrames / 2)) {
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+
+                it.mSurfaceProxy.SurfaceQueueBuffer(0)
+                it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            }
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+    }
+
+    @Test
+    fun testSetBufferCount_queueMaxBufferCountMinusOne() {
+        val numBufferCount = 8
+        val numFrames = numBufferCount * 5L
+        val trace = withTrace {
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetBufferCount(numBufferCount + 1))
+            for (i in 1..numFrames / numBufferCount) {
+                for (bufferSlot in 0..numBufferCount - 1) {
+                    assertEquals(0,
+                            it.mSurfaceProxy.SurfaceDequeueBuffer(bufferSlot, 1000 /* ms */))
+                }
+
+                for (bufferSlot in 0..numBufferCount - 1) {
+                    it.mSurfaceProxy.SurfaceQueueBuffer(bufferSlot)
+                }
+            }
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
new file mode 100644
index 0000000..95a7fd5
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+    @Test
+    fun testSetBuffersGeometry_0x0_rejectsBuffer() {
+        val trace = withTrace {
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+                    R8G8B8A8_UNORM)
+            it.mSurfaceProxy.ANativeWindowLock()
+            it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+            it.mSurfaceProxy.ANativeWindowLock()
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
+            // Submit buffer one with a different size which should be rejected
+            it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+
+            // submit a buffer with the default buffer size
+            it.mSurfaceProxy.ANativeWindowLock()
+            it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+            it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */)
+        }
+        // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+
+        // Verify the next buffer is submitted with the correct size
+        assertThat(trace).layer("SurfaceView", 3).also {
+            it.hasBufferSize(defaultBufferSize)
+            // scaling mode is not passed down to the layer for blast
+            if (useBlastAdapter) {
+                it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+            } else {
+                it.hasScalingMode(ScalingMode.FREEZE.ordinal)
+            }
+        }
+    }
+
+    @Test
+    fun testSetBufferScalingMode_freeze() {
+        val bufferSize = Point(300, 200)
+        val trace = withTrace {
+            it.drawFrame()
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+                    R8G8B8A8_UNORM)
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+            // Change buffer size and set scaling mode to freeze
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+                    R8G8B8A8_UNORM)
+
+            // first dequeued buffer does not have the new size so it should be rejected.
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+            it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+        assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+    }
+
+    @Test
+    fun testSetBufferScalingMode_freeze_withBufferRotation() {
+        val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x)
+        val trace = withTrace {
+            it.drawFrame()
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize,
+                    R8G8B8A8_UNORM)
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+            // Change buffer size and set scaling mode to freeze
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+                    R8G8B8A8_UNORM)
+
+            // first dequeued buffer does not have the new size so it should be rejected.
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            // add a buffer transform so the buffer size is correct.
+            it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90)
+            it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+        assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+        assertThat(trace).layer("SurfaceView", 3).hasBufferOrientation(Transform.ROT_90.value)
+    }
+
+    @Test
+    fun testRejectedBuffersAreReleased() {
+        val bufferSize = Point(300, 200)
+        val trace = withTrace {
+            for (count in 0 until 5) {
+                it.drawFrame()
+                assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 1L,
+                        500 /* ms */), 0)
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+                        R8G8B8A8_UNORM)
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+                // Change buffer size and set scaling mode to freeze
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+                        R8G8B8A8_UNORM)
+
+                // first dequeued buffer does not have the new size so it should be rejected.
+                it.mSurfaceProxy.SurfaceQueueBuffer(0)
+                it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+                it.mSurfaceProxy.SurfaceQueueBuffer(1)
+                assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 3L,
+                        500 /* ms */), 0)
+            }
+        }
+
+        for (count in 0 until 5) {
+            assertThat(trace).layer("SurfaceView", (count * 3) + 1L)
+                    .hasBufferSize(defaultBufferSize)
+            assertThat(trace).layer("SurfaceView", (count * 3) + 2L)
+                    .doesNotExist()
+            assertThat(trace).layer("SurfaceView", (count * 3) + 3L)
+                    .hasBufferSize(bufferSize)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
new file mode 100644
index 0000000..03f8c05
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+    @Test
+    fun testSetBuffersGeometry_0x0_resetsBufferSize() {
+        val trace = withTrace {
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
+                    R8G8B8A8_UNORM)
+            it.mSurfaceProxy.ANativeWindowLock()
+            it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+            it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+    }
+
+    @Test
+    fun testSetBuffersGeometry_smallerThanBuffer() {
+        val bufferSize = Point(300, 200)
+        val trace = withTrace {
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+                    R8G8B8A8_UNORM)
+            it.drawFrame()
+            it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+        }
+
+        assertThat(trace).layer("SurfaceView", 1).also {
+            it.hasBufferSize(bufferSize)
+            it.hasLayerSize(defaultBufferSize)
+            it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+        }
+    }
+
+    @Test
+    fun testSetBuffersGeometry_largerThanBuffer() {
+        val bufferSize = Point(3000, 2000)
+        val trace = withTrace {
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+                    R8G8B8A8_UNORM)
+            it.drawFrame()
+            it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */)
+        }
+
+        assertThat(trace).layer("SurfaceView", 1).also {
+            it.hasBufferSize(bufferSize)
+            it.hasLayerSize(defaultBufferSize)
+            it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal)
+        }
+    }
+
+    @Test
+    fun testSetBufferScalingMode_freeze() {
+        val bufferSize = Point(300, 200)
+        val trace = withTrace {
+            it.drawFrame()
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize,
+                    R8G8B8A8_UNORM)
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+            // Change buffer size and set scaling mode to freeze
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+                    R8G8B8A8_UNORM)
+
+            // first dequeued buffer does not have the new size so it should be rejected.
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW)
+            it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+        assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize)
+    }
+
+    @Test
+    fun testSetBuffersTransform_FLIP() {
+        val transforms = arrayOf(Transform.FLIP_H, Transform.FLIP_V, Transform.ROT_180).withIndex()
+        for ((index, transform) in transforms) {
+            val trace = withTrace {
+                it.mSurfaceProxy.ANativeWindowSetBuffersTransform(transform)
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+                it.mSurfaceProxy.waitUntilBufferDisplayed(index + 1L, 500 /* ms */)
+            }
+
+            assertThat(trace).layer("SurfaceView", index + 1L).also {
+                it.hasBufferSize(defaultBufferSize)
+                it.hasLayerSize(defaultBufferSize)
+                it.hasBufferOrientation(transform.value)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
new file mode 100644
index 0000000..eac3041
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Point
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+import junit.framework.Assert.assertEquals
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class InverseDisplayTransformTests(useBlastAdapter: Boolean) :
+        SurfaceTracingTestBase(useBlastAdapter) {
+    @Before
+    override fun setup() {
+        scenarioRule.getScenario().onActivity {
+            it.rotate90()
+        }
+        instrumentation.waitForIdleSync()
+        super.setup()
+    }
+
+    @Test
+    fun testSetBufferScalingMode_freeze_withInvDisplayTransform() {
+        assumeFalse("Blast does not support buffer rejection with Inv display " +
+                "transform since the only user for this hidden api is camera which does not use" +
+                "fixed scaling mode.", useBlastAdapter)
+
+        val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x)
+        val trace = withTrace {
+            // Inverse display transforms are sticky AND they are only consumed by the sf after
+            // a valid buffer has been acquired.
+            it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.INVERSE_DISPLAY.value)
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0)
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize,
+                    R8G8B8A8_UNORM)
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */))
+            // Change buffer size and set scaling mode to freeze
+            it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0),
+                    R8G8B8A8_UNORM)
+
+            // first dequeued buffer does not have the new size so it should be rejected.
+            it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90.value)
+            it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            it.mSurfaceProxy.ANativeWindowSetBuffersTransform(0)
+            it.mSurfaceProxy.SurfaceQueueBuffer(1)
+            assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0)
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+        assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
index b1e1336..ed79054 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
@@ -15,51 +15,80 @@
  */
 package com.android.test
 
-import android.app.Activity
 import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
 import android.graphics.Color
 import android.graphics.Paint
+import android.graphics.Point
 import android.graphics.Rect
 import android.os.Bundle
 import android.view.Gravity
 import android.view.Surface
 import android.view.SurfaceHolder
 import android.view.SurfaceView
+import android.view.View
+import android.view.WindowManager
+import android.view.cts.surfacevalidator.CapturedActivity
 import android.widget.FrameLayout
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.locks.ReentrantLock
 import kotlin.concurrent.withLock
 
-class MainActivity : Activity() {
+class MainActivity : CapturedActivity() {
     val mSurfaceProxy = SurfaceProxy()
     private var mSurfaceHolder: SurfaceHolder? = null
     private val mDrawLock = ReentrantLock()
+    var mSurfaceView: SurfaceView? = null
 
     val surface: Surface? get() = mSurfaceHolder?.surface
 
-    public override fun onCreate(savedInstanceState: Bundle?) {
+    override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        addSurfaceView(Rect(0, 0, 500, 200))
+        addSurfaceView(Point(500, 200))
+        window.decorView.apply {
+            systemUiVisibility =
+                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN
+        }
     }
 
-    fun addSurfaceView(size: Rect): CountDownLatch {
+    override fun getCaptureDurationMs(): Long {
+        return 30000
+    }
+
+    fun addSurfaceView(size: Point): CountDownLatch {
         val layout = findViewById<FrameLayout>(android.R.id.content)
         val surfaceReadyLatch = CountDownLatch(1)
-        val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
-        layout.addView(surfaceView,
-                FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT)
+        mSurfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
+        layout.addView(mSurfaceView!!,
+                FrameLayout.LayoutParams(size.x, size.y, Gravity.TOP or Gravity.LEFT)
                         .also { it.setMargins(100, 100, 0, 0) })
+
         return surfaceReadyLatch
     }
 
+    fun enableSeamlessRotation() {
+        val p: WindowManager.LayoutParams = window.attributes
+        p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+        window.attributes = p
+    }
+
+    fun rotate90() {
+        if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+        } else {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+        }
+    }
+
     private fun createSurfaceView(
         context: Context,
-        size: Rect,
+        size: Point,
         surfaceReadyLatch: CountDownLatch
     ): SurfaceView {
         val surfaceView = SurfaceView(context)
         surfaceView.setWillNotDraw(false)
-        surfaceView.holder.setFixedSize(size.width(), size.height())
+        surfaceView.holder.setFixedSize(size.x, size.y)
         surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
             override fun surfaceCreated(holder: SurfaceHolder) {
                 mDrawLock.withLock {
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
new file mode 100644
index 0000000..df3d30e
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.server.wm.WindowManagerState.getLogicalDisplaySize
+import android.view.cts.surfacevalidator.CapturedActivity
+import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase
+import android.view.cts.surfacevalidator.PixelChecker
+import android.view.cts.surfacevalidator.RectChecker
+import android.widget.FrameLayout
+import androidx.test.rule.ActivityTestRule
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import java.util.concurrent.CountDownLatch
+
+open class ScreenRecordTestBase(useBlastAdapter: Boolean) :
+        SurfaceViewBufferTestBase(useBlastAdapter) {
+    @get:Rule
+    var mActivityRule = ActivityTestRule(MainActivity::class.java)
+
+    private lateinit var mActivity: MainActivity
+
+    @Before
+    override fun setup() {
+        super.setup()
+        mActivity = mActivityRule.launchActivity(Intent())
+        lateinit var surfaceReadyLatch: CountDownLatch
+        runOnUiThread {
+            it.dismissPermissionDialog()
+            it.setLogicalDisplaySize(getLogicalDisplaySize())
+            surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+        }
+        surfaceReadyLatch.await()
+        // sleep to finish animations
+        instrumentation.waitForIdleSync()
+    }
+
+    @After
+    override fun teardown() {
+        super.teardown()
+        mActivityRule.finishActivity()
+    }
+
+    fun runOnUiThread(predicate: (it: MainActivity) -> Unit) {
+        mActivityRule.runOnUiThread {
+            predicate(mActivity)
+        }
+    }
+
+    fun withScreenRecording(
+        boundsToCheck: Rect,
+        @ColorInt color: Int,
+        predicate: (it: MainActivity) -> Unit
+    ): CapturedActivity.TestResult {
+        val testCase = object : ISurfaceValidatorTestCase {
+            override fun getChecker(): PixelChecker = RectChecker(boundsToCheck, color)
+            override fun start(context: Context, parent: FrameLayout) {
+                predicate(mActivity)
+            }
+            override fun end() { /* do nothing */ }
+        }
+
+        return mActivity.runTest(testCase)
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
new file mode 100644
index 0000000..996a1d3
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.SystemClock
+import android.view.cts.surfacevalidator.PixelColor
+import junit.framework.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class SharedBufferModeScreenRecordTests(useBlastAdapter: Boolean) :
+        ScreenRecordTestBase(useBlastAdapter) {
+
+    /** When auto refresh is set, surface flinger will wake up and refresh the display presenting
+     * the latest content in the buffer.
+     */
+    @Test
+    fun testAutoRefresh() {
+        var svBounds = Rect()
+        runOnUiThread {
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+            it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */)
+            assertEquals(0,
+                    it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */))
+
+            svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height)
+            val position = Rect()
+            it.mSurfaceView!!.getBoundsOnScreen(position)
+            svBounds.offsetTo(position.left, position.top)
+
+            // wait for buffers from other layers to be latched and transactions to be processed before
+            // updating the buffer
+            SystemClock.sleep(4000)
+        }
+
+        val result = withScreenRecording(svBounds, PixelColor.RED) {
+            it.mSurfaceProxy.drawBuffer(0, Color.RED)
+        }
+        val failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames)
+
+        assertTrue("Error: " + result.failFrames +
+                " incorrect frames observed (out of " + (result.failFrames + result.passFrames) +
+                " frames)", failRatio < 0.05)
+        assertTrue("Error: Did not receive sufficient frame updates expected: >1000 actual:" +
+                result.passFrames, result.passFrames > 1000)
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
new file mode 100644
index 0000000..ae662506
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.graphics.Color
+import android.graphics.Rect
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) {
+    /** Sanity test to check each buffer is presented if its submitted with enough delay
+     * for SF to present the buffers. */
+    @Test
+    fun testCanPresentBuffers() {
+        val numFrames = 15L
+        val trace = withTrace {
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+            for (i in 1..numFrames) {
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+                it.mSurfaceProxy.SurfaceQueueBuffer(0)
+                assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(i, 5000 /* ms */))
+            }
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
+    }
+
+    /** Submit buffers as fast as possible testing that we are not blocked when dequeuing the buffer
+     * by setting the dequeue timeout to 1ms and checking that we present the newest buffer. */
+    @Test
+    fun testFastQueueBuffers() {
+        val numFrames = 15L
+        val trace = withTrace {
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+            for (i in 1..numFrames) {
+                assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+                it.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */))
+        }
+
+        assertThat(trace).hasFrameSequence("SurfaceView", numFrames..numFrames)
+    }
+
+    /** Keep overwriting the buffer without queuing buffers and check that we present the latest
+     * buffer content. */
+    @Test
+    fun testAutoRefresh() {
+        var svBounds = Rect()
+        runOnUiThread {
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true))
+            assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true))
+            assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */))
+            it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */)
+            assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */))
+
+            svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height)
+            val position = Rect()
+            it.mSurfaceView!!.getBoundsOnScreen(position)
+            svBounds.offsetTo(position.left, position.top)
+        }
+
+        runOnUiThread {
+            it.mSurfaceProxy.drawBuffer(0, Color.RED)
+            checkPixels(svBounds, Color.RED)
+            it.mSurfaceProxy.drawBuffer(0, Color.GREEN)
+            checkPixels(svBounds, Color.GREEN)
+            it.mSurfaceProxy.drawBuffer(0, Color.BLUE)
+            checkPixels(svBounds, Color.BLUE)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index 884aae4..cfbd3ac 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -16,17 +16,46 @@
 
 package com.android.test
 
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.graphics.Point
+import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode
+import com.android.test.SurfaceViewBufferTestBase.Companion.Transform
+
 class SurfaceProxy {
     init {
         System.loadLibrary("surface_jni")
     }
 
     external fun setSurface(surface: Any)
-    external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int)
+    external fun waitUntilBufferDisplayed(frameNumber: Long, timeoutMs: Int): Int
     external fun draw()
+    fun drawBuffer(slot: Int, @ColorInt c: Int) {
+        drawBuffer(slot, intArrayOf(Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c)))
+    }
+    external fun drawBuffer(slot: Int, color: IntArray)
 
     // android/native_window.h functions
     external fun ANativeWindowLock()
     external fun ANativeWindowUnlockAndPost()
+    fun ANativeWindowSetBuffersGeometry(surface: Any, size: Point, format: Int) {
+        ANativeWindowSetBuffersGeometry(surface, size.x, size.y, format)
+    }
     external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int)
+    fun ANativeWindowSetBuffersTransform(transform: Transform) {
+        ANativeWindowSetBuffersTransform(transform.value)
+    }
+    external fun ANativeWindowSetBuffersTransform(transform: Int)
+
+    // gui/Surface.h functions
+    fun SurfaceSetScalingMode(scalingMode: ScalingMode) {
+        SurfaceSetScalingMode(scalingMode.ordinal)
+    }
+    external fun SurfaceSetScalingMode(scalingMode: Int)
+    external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
+    external fun SurfaceCancelBuffer(slot: Int)
+    external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+    external fun NativeWindowSetBufferCount(count: Int): Int
+    external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
+    external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
 }
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
new file mode 100644
index 0000000..cd4b385
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.annotation.ColorInt
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.util.Log
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.withSFTracing
+import com.android.server.wm.flicker.traces.layers.LayersTrace
+import junit.framework.Assert
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+
+open class SurfaceTracingTestBase(useBlastAdapter: Boolean) :
+        SurfaceViewBufferTestBase(useBlastAdapter) {
+    @get:Rule
+    var scenarioRule: ActivityScenarioRule<MainActivity> =
+            ActivityScenarioRule<MainActivity>(MainActivity::class.java)
+
+    @Before
+    override fun setup() {
+        super.setup()
+        stopLayerTrace()
+        addSurfaceView()
+    }
+
+    @After
+    override fun teardown() {
+        super.teardown()
+        scenarioRule.getScenario().close()
+    }
+
+    fun withTrace(predicate: (it: MainActivity) -> Unit): LayersTrace {
+        return withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                predicate(it)
+            }
+        }
+    }
+
+    fun runOnUiThread(predicate: (it: MainActivity) -> Unit) {
+        scenarioRule.getScenario().onActivity {
+            predicate(it)
+        }
+    }
+
+    private fun addSurfaceView() {
+        lateinit var surfaceReadyLatch: CountDownLatch
+        scenarioRule.getScenario().onActivity {
+            surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+        }
+        surfaceReadyLatch.await()
+        // sleep to finish animations
+        instrumentation.waitForIdleSync()
+    }
+
+    private fun stopLayerTrace() {
+        val tmpDir = instrumentation.targetContext.dataDir.toPath()
+        LayersTraceMonitor(tmpDir).stop()
+    }
+
+    fun checkPixels(bounds: Rect, @ColorInt color: Int) {
+        val screenshot = instrumentation.getUiAutomation().takeScreenshot()
+        val pixels = IntArray(screenshot.width * screenshot.height)
+        screenshot.getPixels(pixels, 0, screenshot.width, 0, 0, screenshot.width, screenshot.height)
+        for (i in bounds.left + 10..bounds.right - 10) {
+            for (j in bounds.top + 10..bounds.bottom - 10) {
+                val actualColor = pixels[j * screenshot.width + i]
+                if (actualColor != color) {
+                    val screenshotPath = instrumentation.targetContext
+                            .getExternalFilesDir(null)?.resolve("screenshot.png")
+                    try {
+                        FileOutputStream(screenshotPath).use { out ->
+                            screenshot.compress(Bitmap.CompressFormat.PNG, 100, out)
+                        }
+                        Log.e("SurfaceViewBufferTests", "Bitmap written to $screenshotPath")
+                    } catch (e: IOException) {
+                        Log.e("SurfaceViewBufferTests", "Error writing bitmap to file", e)
+                    }
+                }
+                Assert.assertEquals("Checking $bounds found mismatch $i,$j",
+                        Color.valueOf(color), Color.valueOf(actualColor))
+            }
+        }
+    }
+
+    private companion object {
+        private const val TRACE_FLAGS =
+                (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
deleted file mode 100644
index b48a91d..0000000
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.test
-
-import android.app.Instrumentation
-import android.graphics.Rect
-import android.provider.Settings
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor
-import com.android.server.wm.flicker.monitor.withSFTracing
-import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import java.util.concurrent.CountDownLatch
-import kotlin.properties.Delegates
-
-@RunWith(Parameterized::class)
-class SurfaceViewBufferTest(val useBlastAdapter: Boolean) {
-    private var mInitialUseBlastConfig by Delegates.notNull<Int>()
-
-    @get:Rule
-    var scenarioRule: ActivityScenarioRule<MainActivity> =
-            ActivityScenarioRule<MainActivity>(MainActivity::class.java)
-
-    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    val defaultBufferSize = Rect(0, 0, 640, 480)
-
-    @Before
-    fun setup() {
-        mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver,
-                "use_blast_adapter_sv", 0)
-        val enable = if (useBlastAdapter) 1 else 0
-        Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
-                enable)
-        val tmpDir = instrumentation.targetContext.dataDir.toPath()
-        LayersTraceMonitor(tmpDir).stop()
-
-        lateinit var surfaceReadyLatch: CountDownLatch
-        scenarioRule.getScenario().onActivity {
-            surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
-        }
-        surfaceReadyLatch.await()
-    }
-
-    @After
-    fun teardown() {
-        scenarioRule.getScenario().close()
-        Settings.Global.putInt(instrumentation.context.contentResolver,
-                "use_blast_adapter_sv", mInitialUseBlastConfig)
-    }
-
-    @Test
-    fun testSetBuffersGeometry_0x0_resetsBufferSize() {
-        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
-            scenarioRule.getScenario().onActivity {
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
-                        R8G8B8A8_UNORM)
-                it.mSurfaceProxy.ANativeWindowLock()
-                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
-            }
-        }
-
-        // verify buffer size is reset to default buffer size
-        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
-    }
-
-    @Test
-    fun testSetBuffersGeometry_0x0_rejectsBuffer() {
-        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
-            scenarioRule.getScenario().onActivity {
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
-                        R8G8B8A8_UNORM)
-                it.mSurfaceProxy.ANativeWindowLock()
-                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-                it.mSurfaceProxy.ANativeWindowLock()
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
-                // Submit buffer one with a different size which should be rejected
-                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-
-                // submit a buffer with the default buffer size
-                it.mSurfaceProxy.ANativeWindowLock()
-                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-                it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */)
-            }
-        }
-        // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
-        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
-
-        // Verify the next buffer is submitted with the correct size
-        assertThat(trace).layer("SurfaceView", 3).also {
-            it.hasBufferSize(defaultBufferSize)
-            it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */)
-        }
-    }
-
-    @Test
-    fun testSetBuffersGeometry_smallerThanBuffer() {
-        val bufferSize = Rect(0, 0, 300, 200)
-        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
-            scenarioRule.getScenario().onActivity {
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
-                        bufferSize.height(), R8G8B8A8_UNORM)
-                it.drawFrame()
-                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
-            }
-        }
-
-        assertThat(trace).layer("SurfaceView", 1).also {
-            it.hasBufferSize(bufferSize)
-            it.hasLayerSize(defaultBufferSize)
-            it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
-        }
-    }
-
-    @Test
-    fun testSetBuffersGeometry_largerThanBuffer() {
-        val bufferSize = Rect(0, 0, 3000, 2000)
-        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
-            scenarioRule.getScenario().onActivity {
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
-                        bufferSize.height(), R8G8B8A8_UNORM)
-                it.drawFrame()
-                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
-            }
-        }
-
-        assertThat(trace).layer("SurfaceView", 1).also {
-            it.hasBufferSize(bufferSize)
-            it.hasLayerSize(defaultBufferSize)
-            it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
-        }
-    }
-
-    /** Submit buffers as fast as possible and make sure they are queued */
-    @Test
-    fun testQueueBuffers() {
-        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
-            scenarioRule.getScenario().onActivity {
-                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
-                        R8G8B8A8_UNORM)
-                for (i in 0..100) {
-                    it.mSurfaceProxy.ANativeWindowLock()
-                    it.mSurfaceProxy.ANativeWindowUnlockAndPost()
-                }
-                it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */)
-            }
-        }
-        for (frameNumber in 1..100) {
-            assertThat(trace).layer("SurfaceView", frameNumber.toLong())
-        }
-    }
-
-    companion object {
-        private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
-        private const val R8G8B8A8_UNORM = 1
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "blast={0}")
-        fun data(): Collection<Array<Any>> {
-            return listOf(
-                    arrayOf(false), // First test:  submit buffers via bufferqueue
-                    arrayOf(true)   // Second test: submit buffers via blast adapter
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
new file mode 100644
index 0000000..093c312
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.provider.Settings
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.rules.TestName
+import org.junit.runners.Parameterized
+import kotlin.properties.Delegates
+
+open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) {
+    private var mInitialBlastConfig by Delegates.notNull<Boolean>()
+
+    val instrumentation: Instrumentation
+        get() = InstrumentationRegistry.getInstrumentation()
+
+    @get:Rule
+    var mName = TestName()
+
+    @Before
+    open fun setup() {
+        mInitialBlastConfig = getBlastAdapterSvEnabled()
+        setBlastAdapterSvEnabled(useBlastAdapter)
+    }
+
+    @After
+    open fun teardown() {
+        setBlastAdapterSvEnabled(mInitialBlastConfig)
+    }
+
+    private fun getBlastAdapterSvEnabled(): Boolean {
+        return Settings.Global.getInt(instrumentation.context.contentResolver,
+                "use_blast_adapter_sv", 0) != 0
+    }
+
+    private fun setBlastAdapterSvEnabled(enable: Boolean) {
+        Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
+                if (enable) 1 else 0)
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "blast={0}")
+        fun data(): Collection<Array<Any>> {
+            return listOf(
+                    arrayOf(false), // First test:  submit buffers via bufferqueue
+                    arrayOf(true)   // Second test: submit buffers via blast adapter
+            )
+        }
+
+        const val R8G8B8A8_UNORM = 1
+        val defaultBufferSize = Point(640, 480)
+
+        // system/window.h definitions
+        enum class ScalingMode() {
+            FREEZE, // = 0
+            SCALE_TO_WINDOW, // =1
+            SCALE_CROP, // = 2
+            NO_SCALE_CROP // = 3
+        }
+
+        // system/window.h definitions
+        enum class Transform(val value: Int) {
+            /* flip source image horizontally */
+            FLIP_H(1),
+            /* flip source image vertically */
+            FLIP_V(2),
+            /* rotate source image 90 degrees clock-wise, and is applied after TRANSFORM_FLIP_{H|V} */
+            ROT_90(4),
+            /* rotate source image 180 degrees */
+            ROT_180(3),
+            /* rotate source image 270 degrees clock-wise */
+            ROT_270(7),
+            /* transforms source by the inverse transform of the screen it is displayed onto. This
+             * transform is applied last */
+            INVERSE_DISPLAY(0x08)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
index abccd6c..fe9deae 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
@@ -16,6 +16,7 @@
 package com.android.test.taskembed
 
 import android.app.Instrumentation
+import android.graphics.Point
 import android.graphics.Rect
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.platform.app.InstrumentationRegistry
@@ -93,13 +94,15 @@
 
         // verify buffer size should be changed to expected values.
         assertThat(trace).layer(FIRST_ACTIVITY, frame).also {
-            it.hasLayerSize(firstBounds)
-            it.hasBufferSize(firstBounds)
+            val firstTaskSize = Point(firstBounds.width(), firstBounds.height())
+            it.hasLayerSize(firstTaskSize)
+            it.hasBufferSize(firstTaskSize)
         }
 
         assertThat(trace).layer(SECOND_ACTIVITY, frame).also {
-            it.hasLayerSize(secondBounds)
-            it.hasBufferSize(secondBounds)
+            val secondTaskSize = Point(secondBounds.width(), secondBounds.height())
+            it.hasLayerSize(secondTaskSize)
+            it.hasBufferSize(secondTaskSize)
         }
     }
 
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index dba1856e..70f6386 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -200,7 +200,8 @@
         nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
         nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
 
-        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
+        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */,
+                context)
         networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
 
         na.addCapability(NET_CAPABILITY_INTERNET)
@@ -238,7 +239,7 @@
 
         val lp = LinkProperties()
         lp.captivePortalApiUrl = Uri.parse(apiUrl)
-        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, context)
+        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, null /* ncTemplate */, context)
         networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
 
         na.addCapability(NET_CAPABILITY_INTERNET)
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 85704d0..2a24d1a 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -72,12 +72,12 @@
     private long mKeepaliveResponseDelay = 0L;
     private Integer mExpectedKeepaliveSlot = null;
 
-    public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context)
-            throws Exception {
+    public NetworkAgentWrapper(int transport, LinkProperties linkProperties,
+            NetworkCapabilities ncTemplate, Context context) throws Exception {
         final int type = transportToLegacyType(transport);
         final String typeName = ConnectivityManager.getNetworkTypeName(type);
         mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
-        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
         mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
diff --git a/tests/net/integration/util/com/android/server/TestNetIdManager.kt b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
index eb290dc..938a694 100644
--- a/tests/net/integration/util/com/android/server/TestNetIdManager.kt
+++ b/tests/net/integration/util/com/android/server/TestNetIdManager.kt
@@ -35,4 +35,5 @@
     private val nextId = AtomicInteger(MAX_NET_ID)
     override fun reserveNetId() = nextId.decrementAndGet()
     override fun releaseNetId(id: Int) = Unit
+    fun peekNextNetId() = nextId.get() - 1
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b78f0e2..4bb1317 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -322,6 +322,7 @@
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
     private static final String WIFI_IFNAME = "test_wlan0";
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
+    private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
@@ -339,6 +340,7 @@
     private INetworkPolicyListener mPolicyListener;
     private WrappedMultinetworkPolicyTracker mPolicyTracker;
     private HandlerThread mAlarmManagerThread;
+    private TestNetIdManager mNetIdManager;
 
     @Mock IIpConnectivityMetrics mIpConnectivityMetrics;
     @Mock IpConnectivityMetrics.Logger mMetricsService;
@@ -617,12 +619,17 @@
         private String mRedirectUrl;
 
         TestNetworkAgentWrapper(int transport) throws Exception {
-            this(transport, new LinkProperties());
+            this(transport, new LinkProperties(), null);
         }
 
         TestNetworkAgentWrapper(int transport, LinkProperties linkProperties)
                 throws Exception {
-            super(transport, linkProperties, mServiceContext);
+            this(transport, linkProperties, null);
+        }
+
+        private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties,
+                NetworkCapabilities ncTemplate) throws Exception {
+            super(transport, linkProperties, ncTemplate, mServiceContext);
 
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
@@ -1017,46 +1024,36 @@
         }
     }
 
+    private Set<UidRange> uidRangesForUid(int uid) {
+        final ArraySet<UidRange> ranges = new ArraySet<>();
+        ranges.add(new UidRange(uid, uid));
+        return ranges;
+    }
+
     private static Looper startHandlerThreadAndReturnLooper() {
         final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
         handlerThread.start();
         return handlerThread.getLooper();
     }
 
-    private class MockVpn extends Vpn {
-        // TODO : the interactions between this mock and the mock network agent are too
-        // hard to get right at this moment, because it's unclear in which case which
-        // target needs to get a method call or both, and in what order. It's because
-        // MockNetworkAgent wants to manage its own NetworkCapabilities, but the Vpn
-        // parent class of MockVpn agent wants that responsibility.
-        // That being said inside the test it should be possible to make the interactions
-        // harder to get wrong with precise speccing, judicious comments, helper methods
-        // and a few sprinkled assertions.
-
-        private boolean mConnected = false;
+    private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
         // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
         // not inherit from NetworkAgent.
         private TestNetworkAgentWrapper mMockNetworkAgent;
-        private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
+        private boolean mAgentRegistered = false;
 
+        private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
         private VpnInfo mVpnInfo;
-        private Network[] mUnderlyingNetworks;
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
                     userId, mock(KeyStore.class));
-        }
-
-        public void setNetworkAgent(TestNetworkAgentWrapper agent) {
-            agent.waitForIdle(TIMEOUT_MS);
-            mMockNetworkAgent = agent;
-            mNetworkAgent = agent.getNetworkAgent();
-            mNetworkCapabilities.set(agent.getNetworkCapabilities());
+            mConfig = new VpnConfig();
         }
 
         public void setUids(Set<UidRange> uids) {
             mNetworkCapabilities.setUids(uids);
-            updateCapabilities(null /* defaultNetwork */);
+            updateCapabilitiesInternal(null /* defaultNetwork */, true);
         }
 
         public void setVpnType(int vpnType) {
@@ -1064,21 +1061,13 @@
         }
 
         @Override
+        public Network getNetwork() {
+            return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork();
+        }
+
+        @Override
         public int getNetId() {
-            if (mMockNetworkAgent == null) {
-                return NETID_UNSET;
-            }
-            return mMockNetworkAgent.getNetwork().netId;
-        }
-
-        @Override
-        public boolean appliesToUid(int uid) {
-            return mConnected;  // Trickery to simplify testing.
-        }
-
-        @Override
-        protected boolean isCallerEstablishedOwnerLocked() {
-            return mConnected;  // Similar trickery
+            return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId;
         }
 
         @Override
@@ -1086,41 +1075,94 @@
             return mVpnType;
         }
 
-        private void connect(boolean isAlwaysMetered) {
-            mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
-            mConnected = true;
-            mConfig = new VpnConfig();
+        private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
+                throws Exception {
+            if (mAgentRegistered) throw new IllegalStateException("already registered");
+            setUids(uids);
             mConfig.isMetered = isAlwaysMetered;
+            mInterface = VPN_IFNAME;
+            mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
+                    mNetworkCapabilities);
+            mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
+            mAgentRegistered = true;
+            mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
+            mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
         }
 
-        public void connectAsAlwaysMetered() {
-            connect(true /* isAlwaysMetered */);
+        private void registerAgent(Set<UidRange> uids) throws Exception {
+            registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties());
         }
 
-        public void connect() {
-            connect(false /* isAlwaysMetered */);
+        private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
+            mMockNetworkAgent.connect(validated, hasInternet, isStrictMode);
+        }
+
+        private void connect(boolean validated) {
+            mMockNetworkAgent.connect(validated);
+        }
+
+        private TestNetworkAgentWrapper getAgent() {
+            return mMockNetworkAgent;
+        }
+
+        public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
+                boolean hasInternet, boolean isStrictMode) throws Exception {
+            mNetworkCapabilities.setOwnerUid(uid);
+            mNetworkCapabilities.setAdministratorUids(new int[]{uid});
+            registerAgent(false, ranges, lp);
+            connect(validated, hasInternet, isStrictMode);
+            waitForIdle();
+        }
+
+        public void establish(LinkProperties lp, int uid, Set<UidRange> ranges) throws Exception {
+            establish(lp, uid, ranges, true, true, false);
+        }
+
+        public void establishForMyUid(LinkProperties lp) throws Exception {
+            final int uid = Process.myUid();
+            establish(lp, uid, uidRangesForUid(uid), true, true, false);
+        }
+
+        public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
+                throws Exception {
+            final int uid = Process.myUid();
+            establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
+                    isStrictMode);
+        }
+
+        public void establishForMyUid() throws Exception {
+            establishForMyUid(new LinkProperties());
+        }
+
+        public void sendLinkProperties(LinkProperties lp) {
+            mMockNetworkAgent.sendLinkProperties(lp);
+        }
+
+        private NetworkCapabilities updateCapabilitiesInternal(Network defaultNetwork,
+                boolean sendToConnectivityService) {
+            if (!mAgentRegistered) return null;
+            super.updateCapabilities(defaultNetwork);
+            // Because super.updateCapabilities will update the capabilities of the agent but
+            // not the mock agent, the mock agent needs to know about them.
+            copyCapabilitiesToNetworkAgent(sendToConnectivityService);
+            return new NetworkCapabilities(mNetworkCapabilities);
+        }
+
+        private void copyCapabilitiesToNetworkAgent(boolean sendToConnectivityService) {
+            if (null != mMockNetworkAgent) {
+                mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities,
+                        sendToConnectivityService);
+            }
         }
 
         @Override
         public NetworkCapabilities updateCapabilities(Network defaultNetwork) {
-            if (!mConnected) return null;
-            super.updateCapabilities(defaultNetwork);
-            // Because super.updateCapabilities will update the capabilities of the agent but
-            // not the mock agent, the mock agent needs to know about them.
-            copyCapabilitiesToNetworkAgent();
-            return new NetworkCapabilities(mNetworkCapabilities);
-        }
-
-        private void copyCapabilitiesToNetworkAgent() {
-            if (null != mMockNetworkAgent) {
-                mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities,
-                        false /* sendToConnectivityService */);
-            }
+            return updateCapabilitiesInternal(defaultNetwork, false);
         }
 
         public void disconnect() {
-            mConnected = false;
-            mConfig = null;
+            if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect();
+            mAgentRegistered = false;
         }
 
         @Override
@@ -1133,18 +1175,6 @@
         private synchronized void setVpnInfo(VpnInfo vpnInfo) {
             mVpnInfo = vpnInfo;
         }
-
-        @Override
-        public synchronized Network[] getUnderlyingNetworks() {
-            if (mUnderlyingNetworks != null) return mUnderlyingNetworks;
-
-            return super.getUnderlyingNetworks();
-        }
-
-        /** Don't override behavior for {@link Vpn#setUnderlyingNetworks}. */
-        private synchronized void overrideUnderlyingNetworks(Network[] underlyingNetworks) {
-            mUnderlyingNetworks = underlyingNetworks;
-        }
     }
 
     private void mockVpn(int uid) {
@@ -1207,6 +1237,8 @@
 
     @Before
     public void setUp() throws Exception {
+        mNetIdManager = new TestNetIdManager();
+
         mContext = InstrumentationRegistry.getContext();
 
         MockitoAnnotations.initMocks(this);
@@ -1277,7 +1309,7 @@
         doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt());
         final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
         doReturn(mCsHandlerThread).when(deps).makeHandlerThread();
-        doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
+        doReturn(mNetIdManager).when(deps).makeNetIdManager();
         doReturn(mNetworkStack).when(deps).getNetworkStack();
         doReturn(mSystemProperties).when(deps).getSystemProperties();
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
@@ -1335,6 +1367,9 @@
             mEthernetNetworkAgent.disconnect();
             mEthernetNetworkAgent = null;
         }
+        mMockVpn.disconnect();
+        waitForIdle();
+
         FakeSettingsProvider.clearSettingsProvider();
 
         mCsHandlerThread.quitSafely();
@@ -3218,20 +3253,12 @@
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
-        final int uid = Process.myUid();
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true);
-        mMockVpn.connect();
-        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+        mMockVpn.establishForMyUid();
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        vpnNetworkAgent.disconnect();
-        defaultNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        mMockVpn.disconnect();
+        defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -4808,13 +4835,52 @@
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
+    private <T> void assertSameElementsNoDuplicates(T[] expected, T[] actual) {
+        // Easier to implement than a proper "assertSameElements" method that also correctly deals
+        // with duplicates.
+        final String msg = Arrays.toString(expected) + " != " + Arrays.toString(actual);
+        assertEquals(msg, expected.length, actual.length);
+        Set expectedSet = new ArraySet<>(Arrays.asList(expected));
+        assertEquals("expected contains duplicates", expectedSet.size(), expected.length);
+        // actual cannot have duplicates because it's the same length and has the same elements.
+        Set actualSet = new ArraySet<>(Arrays.asList(actual));
+        assertEquals(expectedSet, actualSet);
+    }
+
+    private void expectForceUpdateIfaces(Network[] networks, String defaultIface,
+            Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception {
+        ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class);
+        ArgumentCaptor<VpnInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(VpnInfo[].class);
+
+        verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(),
+                any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture());
+
+        assertSameElementsNoDuplicates(networksCaptor.getValue(), networks);
+
+        VpnInfo[] infos = vpnInfosCaptor.getValue();
+        if (vpnUid != null) {
+            assertEquals("Should have exactly one VPN:", 1, infos.length);
+            VpnInfo info = infos[0];
+            assertEquals("Unexpected VPN owner:", (int) vpnUid, info.ownerUid);
+            assertEquals("Unexpected VPN interface:", vpnIfname, info.vpnIface);
+            assertSameElementsNoDuplicates(underlyingIfaces, info.underlyingIfaces);
+        } else {
+            assertEquals(0, infos.length);
+            return;
+        }
+    }
+
+    private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception {
+        expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]);
+    }
+
     @Test
     public void testStatsIfacesChanged() throws Exception {
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
-        Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()};
-        Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()};
+        final Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()};
+        final Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()};
 
         LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -4825,9 +4891,7 @@
         mCellNetworkAgent.connect(false);
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
-                        eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
         reset(mStatsService);
 
         // Default network switch should update ifaces.
@@ -4835,32 +4899,24 @@
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
         waitForIdle();
         assertEquals(wifiLp, mService.getActiveLinkProperties());
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyWifi), any(NetworkState[].class), eq(WIFI_IFNAME),
-                        eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME);
         reset(mStatsService);
 
         // Disconnect should update ifaces.
         mWiFiNetworkAgent.disconnect();
         waitForIdle();
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class),
-                        eq(MOBILE_IFNAME), eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
         reset(mStatsService);
 
         // Metered change should update ifaces
         mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
-                        eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
         reset(mStatsService);
 
         mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
-                        eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
         reset(mStatsService);
 
         // Captive portal change shouldn't update ifaces
@@ -4874,9 +4930,102 @@
         // Roaming change should update ifaces
         mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         waitForIdle();
-        verify(mStatsService, atLeastOnce())
-                .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
-                        eq(new VpnInfo[0]));
+        expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME);
+        reset(mStatsService);
+
+        // Test VPNs.
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(VPN_IFNAME);
+
+        mMockVpn.establishForMyUid(lp);
+
+        final Network[] cellAndVpn = new Network[] {
+                mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()};
+        Network[] cellAndWifi = new Network[] {
+                mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()};
+
+        // A VPN with default (null) underlying networks sets the underlying network's interfaces...
+        expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+                new String[]{MOBILE_IFNAME});
+
+        // ...and updates them as the default network switches.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        final Network[] wifiAndVpn = new Network[] {
+                mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()};
+        cellAndWifi = new Network[] {
+                mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()};
+
+        waitForIdle();
+        assertEquals(wifiLp, mService.getActiveLinkProperties());
+        expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME,
+                new String[]{WIFI_IFNAME});
+        reset(mStatsService);
+
+        // A VPN that sets its underlying networks passes the underlying interfaces, and influences
+        // the default interface sent to NetworkStatsService by virtue of applying to the system
+        // server UID (or, in this test, to the test's UID). This is the reason for sending
+        // MOBILE_IFNAME even though the default network is wifi.
+        // TODO: fix this to pass in the actual default network interface. Whether or not the VPN
+        // applies to the system server UID should not have any bearing on network stats.
+        mService.setUnderlyingNetworksForVpn(onlyCell);
+        waitForIdle();
+        expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+                new String[]{MOBILE_IFNAME});
+        reset(mStatsService);
+
+        mService.setUnderlyingNetworksForVpn(cellAndWifi);
+        waitForIdle();
+        expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+                new String[]{MOBILE_IFNAME, WIFI_IFNAME});
+        reset(mStatsService);
+
+        // If an underlying network disconnects, that interface should no longer be underlying.
+        // This doesn't actually work because disconnectAndDestroyNetwork only notifies
+        // NetworkStatsService before the underlying network is actually removed. So the underlying
+        // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This
+        // could result in incorrect data usage measurements if the interface used by the
+        // disconnected network is reused by a system component that does not register an agent for
+        // it (e.g., tethering).
+        mCellNetworkAgent.disconnect();
+        waitForIdle();
+        assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork()));
+        expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
+                new String[]{MOBILE_IFNAME, WIFI_IFNAME});
+
+        // Confirm that we never tell NetworkStatsService that cell is no longer the underlying
+        // network for the VPN...
+        verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class),
+                any(NetworkState[].class), any() /* anyString() doesn't match null */,
+                argThat(infos -> infos[0].underlyingIfaces.length == 1
+                        && WIFI_IFNAME.equals(infos[0].underlyingIfaces[0])));
+        verifyNoMoreInteractions(mStatsService);
+        reset(mStatsService);
+
+        // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be
+        // called again, it does. For example, connect Ethernet, but with a low score, such that it
+        // does not become the default network.
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.adjustScore(-40);
+        mEthernetNetworkAgent.connect(false);
+        waitForIdle();
+        verify(mStatsService).forceUpdateIfaces(any(Network[].class),
+                any(NetworkState[].class), any() /* anyString() doesn't match null */,
+                argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.length == 1
+                        && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces[0])));
+        mEthernetNetworkAgent.disconnect();
+        reset(mStatsService);
+
+        // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo
+        // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes
+        // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which
+        // is probably a performance improvement (though it's very unlikely that a VPN would declare
+        // no underlying networks).
+        // Also, for the same reason as above, the active interface passed in is null.
+        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        waitForIdle();
+        expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
     }
 
@@ -5232,6 +5381,58 @@
     }
 
     @Test
+    public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_NOT_VPN).build();
+
+        mCm.registerNetworkCallback(request, callback);
+
+        // Bring up a VPN that specifies an underlying network that does not exist yet.
+        // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet,
+        // (and doing so is difficult without using reflection) but it's good to test that the code
+        // behaves approximately correctly.
+        mMockVpn.establishForMyUid(false, true, false);
+        final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
+        mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork});
+        callback.expectAvailableCallbacksUnvalidated(mMockVpn);
+        assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+                .hasTransport(TRANSPORT_VPN));
+        assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+                .hasTransport(TRANSPORT_WIFI));
+
+        // Make that underlying network connect, and expect to see its capabilities immediately
+        // reflected in the VPN's capabilities.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork());
+        mWiFiNetworkAgent.connect(false);
+        // TODO: the callback for the VPN happens before any callbacks are called for the wifi
+        // network that has just connected. There appear to be two issues here:
+        // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for
+        //    it returns non-null (which happens very early, during handleRegisterNetworkAgent).
+        //    This is not correct because that that point the network is not connected and cannot
+        //    pass any traffic.
+        // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities
+        //    before rematching networks.
+        // Given that this scenario can't really happen, this is probably fine for now.
+        callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+                .hasTransport(TRANSPORT_VPN));
+        assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
+                .hasTransport(TRANSPORT_WIFI));
+
+        // Disconnect the network, and expect to see the VPN capabilities change accordingly.
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        callback.expectCapabilitiesThat(mMockVpn, (nc) ->
+                nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN));
+
+        mMockVpn.disconnect();
+        mCm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
     public void testVpnNetworkActive() throws Exception {
         final int uid = Process.myUid();
 
@@ -5265,42 +5466,38 @@
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
+        final Set<UidRange> ranges = uidRangesForUid(uid);
+        mMockVpn.registerAgent(ranges);
+
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
         assertFalse(NetworkMonitorUtils.isValidationRequired(
-                vpnNetworkAgent.getNetworkCapabilities()));
-        vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
+                mMockVpn.getAgent().getNetworkCapabilities()));
+        mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
 
-        vpnNetworkAgent.connect(false);
-        mMockVpn.connect();
-        mMockVpn.setUnderlyingNetworks(new Network[0]);
+        mMockVpn.connect(false);
+        mService.setUnderlyingNetworksForVpn(new Network[0]);
 
-        genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+        vpnNetworkCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mMockVpn);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        genericNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids());
-        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, nc -> null == nc.getUids());
+        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         ranges.clear();
-        vpnNetworkAgent.setUids(ranges);
+        mMockVpn.setUids(ranges);
 
-        genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
 
         // TODO : The default network callback should actually get a LOST call here (also see the
         // comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -5308,19 +5505,18 @@
         // can't currently update their UIDs without disconnecting, so this does not matter too
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
-        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.setUids(ranges);
 
-        genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+        vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn);
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
-        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
 
         mWiFiNetworkAgent.disconnect();
 
@@ -5330,13 +5526,13 @@
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
 
-        vpnNetworkAgent.disconnect();
+        mMockVpn.disconnect();
 
-        genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
-        defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
@@ -5358,20 +5554,13 @@
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+        mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
                 false /* isStrictMode */);
-        mMockVpn.connect();
 
         defaultCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        vpnNetworkAgent.disconnect();
+        mMockVpn.disconnect();
         defaultCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5390,21 +5579,14 @@
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */,
+        mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */,
                 false /* isStrictMode */);
-        mMockVpn.connect();
 
-        defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        vpnNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        mMockVpn.disconnect();
+        defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5422,44 +5604,36 @@
         callback.assertNoCallback();
 
         // Bring up a VPN that has the INTERNET capability, initially unvalidated.
-        final int uid = Process.myUid();
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */,
+        mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
                 false /* isStrictMode */);
-        mMockVpn.connect();
 
         // Even though the VPN is unvalidated, it becomes the default network for our app.
-        callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mMockVpn);
         callback.assertNoCallback();
 
-        assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
-        assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore());
-        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore());
+        assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore());
+        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
-        NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+        NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
 
         assertFalse(NetworkMonitorUtils.isValidationRequired(
-                vpnNetworkAgent.getNetworkCapabilities()));
+                mMockVpn.getAgent().getNetworkCapabilities()));
         assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
-                vpnNetworkAgent.getNetworkCapabilities()));
+                mMockVpn.getAgent().getNetworkCapabilities()));
 
         // Pretend that the VPN network validates.
-        vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
-        vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+        mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid());
         // Expect to see the validated capability, but no other changes, because the VPN is already
         // the default network for the app.
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn);
         callback.assertNoCallback();
 
-        vpnNetworkAgent.disconnect();
-        callback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent);
+        mMockVpn.disconnect();
+        callback.expectCallback(CallbackEntry.LOST, mMockVpn);
         callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
     }
 
@@ -5481,21 +5655,15 @@
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mCellNetworkAgent.connect(true);
 
-        final TestNetworkAgentWrapper vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.connect();
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+        mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
                 false /* isStrictMode */);
 
-        vpnNetworkCallback.expectAvailableCallbacks(vpnNetworkAgent.getNetwork(),
+        vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(),
                 false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS);
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent.getNetwork(), TIMEOUT_MS,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS,
                 nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED));
 
-        final NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertTrue(nc.hasTransport(TRANSPORT_VPN));
         assertTrue(nc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5517,18 +5685,11 @@
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         vpnNetworkCallback.assertNoCallback();
 
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.connect();
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+        mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
                 false /* isStrictMode */);
 
-        vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
-        nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+        vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertTrue(nc.hasTransport(TRANSPORT_VPN));
         assertFalse(nc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5545,7 +5706,7 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5559,7 +5720,7 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5569,7 +5730,7 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5577,27 +5738,27 @@
 
         // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended.
         mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn);
 
         // Add NOT_SUSPENDED again and observe VPN is no longer suspended.
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
                 && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
 
         // Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5607,7 +5768,7 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5620,7 +5781,7 @@
         // Stop using WiFi. The VPN is suspended again.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5634,7 +5795,7 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -5645,14 +5806,14 @@
         // Disconnect cell. Receive update without even removing the dead network from the
         // underlying networks – it's dead anyway. Not metered any more.
         mCellNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect wifi too. No underlying networks means this is now metered.
         mWiFiNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5673,18 +5834,11 @@
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         vpnNetworkCallback.assertNoCallback();
 
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.connect();
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+        mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
                 false /* isStrictMode */);
 
-        vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
-        nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+        vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertTrue(nc.hasTransport(TRANSPORT_VPN));
         assertFalse(nc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(nc.hasTransport(TRANSPORT_WIFI));
@@ -5696,7 +5850,7 @@
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5706,7 +5860,7 @@
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5718,7 +5872,7 @@
         // Disconnect wifi too. Now we have no default network.
         mWiFiNetworkAgent.disconnect();
 
-        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -5761,18 +5915,10 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // Connect VPN network. By default it is using current default network (Cell).
-        TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        final int uid = Process.myUid();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true);
-        mMockVpn.connect();
-        waitForIdle();
+        mMockVpn.establishForMyUid();
+
         // Ensure VPN is now the active network.
-        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         // Expect VPN to be metered.
         assertTrue(mCm.isActiveNetworkMetered());
@@ -5783,7 +5929,7 @@
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
         // VPN should still be the active network.
-        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         // Expect VPN to be unmetered as it should now be using WiFi (new default).
         assertFalse(mCm.isActiveNetworkMetered());
@@ -5801,7 +5947,6 @@
         // VPN without any underlying networks is treated as metered.
         assertTrue(mCm.isActiveNetworkMetered());
 
-        vpnNetworkAgent.disconnect();
         mMockVpn.disconnect();
     }
 
@@ -5822,18 +5967,10 @@
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Connect VPN network.
-        TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        final int uid = Process.myUid();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true);
-        mMockVpn.connect();
-        waitForIdle();
+        mMockVpn.establishForMyUid();
+
         // Ensure VPN is now the active network.
-        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         // VPN is using Cell
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
@@ -5873,7 +6010,6 @@
         // VPN without underlying networks is treated as metered.
         assertTrue(mCm.isActiveNetworkMetered());
 
-        vpnNetworkAgent.disconnect();
         mMockVpn.disconnect();
     }
 
@@ -5888,17 +6024,11 @@
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Connect VPN network.
-        TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        final int uid = Process.myUid();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true);
-        mMockVpn.connectAsAlwaysMetered();
+        mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUid(Process.myUid()),
+                new LinkProperties());
+        mMockVpn.connect(true);
         waitForIdle();
-        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         // VPN is tracking current platform default (WiFi).
         mService.setUnderlyingNetworksForVpn(null);
@@ -5922,7 +6052,7 @@
 
         assertTrue(mCm.isActiveNetworkMetered());
 
-        vpnNetworkAgent.disconnect();
+        mMockVpn.disconnect();
     }
 
     @Test
@@ -6654,34 +6784,21 @@
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
 
-        // Set up a VPN network with a proxy
-        final int uid = Process.myUid();
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        mMockVpn.setUids(ranges);
+        // Connect a VPN network with a proxy.
         LinkProperties testLinkProperties = new LinkProperties();
         testLinkProperties.setHttpProxy(testProxyInfo);
-        vpnNetworkAgent.sendLinkProperties(testLinkProperties);
-        waitForIdle();
-
-        // Connect to VPN with proxy
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        vpnNetworkAgent.connect(true);
-        mMockVpn.connect();
-        waitForIdle();
+        mMockVpn.establishForMyUid(testLinkProperties);
 
         // Test that the VPN network returns a proxy, and the WiFi does not.
-        assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork()));
         assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
         assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
 
         // Test that the VPN network returns no proxy when it is set to null.
         testLinkProperties.setHttpProxy(null);
-        vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+        mMockVpn.sendLinkProperties(testLinkProperties);
         waitForIdle();
-        assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+        assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork()));
         assertNull(mService.getProxyForNetwork(null));
 
         // Set WiFi proxy and check that the vpn proxy is still null.
@@ -6692,7 +6809,7 @@
 
         // Disconnect from VPN and check that the active network, which is now the WiFi, has the
         // correct proxy setting.
-        vpnNetworkAgent.disconnect();
+        mMockVpn.disconnect();
         waitForIdle();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
@@ -6707,7 +6824,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+        mMockVpn.establish(lp, VPN_UID, vpnRange);
 
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
@@ -6717,7 +6834,7 @@
         assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
         assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
 
-        vpnNetworkAgent.disconnect();
+        mMockVpn.disconnect();
         waitForIdle();
 
         // Disconnected VPN should have interface rules removed
@@ -6734,8 +6851,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
-                lp, Process.SYSTEM_UID, vpnRange);
+        mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
 
         // Legacy VPN should not have interface rules set up
         verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6750,8 +6866,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(
-                lp, Process.SYSTEM_UID, vpnRange);
+        mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
 
         // IPv6 unreachable route should not be misinterpreted as a default route
         verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
@@ -6765,7 +6880,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+        mMockVpn.establish(lp, VPN_UID, vpnRange);
 
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
@@ -6777,7 +6892,7 @@
         reset(mMockNetd);
         InOrder inOrder = inOrder(mMockNetd);
         lp.setInterfaceName("tun1");
-        vpnNetworkAgent.sendLinkProperties(lp);
+        mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN handover (switch to a new interface) should result in rules being updated (old rules
         // removed first, then new rules added)
@@ -6790,7 +6905,7 @@
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
-        vpnNetworkAgent.sendLinkProperties(lp);
+        mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN not routing everything should no longer have interface filtering rules
         verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
@@ -6801,7 +6916,7 @@
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
-        vpnNetworkAgent.sendLinkProperties(lp);
+        mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // Back to routing all IPv6 traffic should have filtering rules
         verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
@@ -6816,8 +6931,7 @@
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final UidRange vpnRange = UidRange.createForUser(VPN_USER);
-        final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID,
-                Collections.singleton(vpnRange));
+        mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange));
 
         reset(mMockNetd);
         InOrder inOrder = inOrder(mMockNetd);
@@ -6826,7 +6940,7 @@
         final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
                 new UidRange(vpnRange.start, APP1_UID - 1),
                 new UidRange(APP1_UID + 1, vpnRange.stop)));
-        vpnNetworkAgent.setUids(newRanges);
+        mMockVpn.setUids(newRanges);
         waitForIdle();
 
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
@@ -6967,7 +7081,7 @@
     private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
             throws Exception {
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
-        establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange);
+        mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
         mMockVpn.setVpnType(vpnType);
 
         final VpnInfo vpnInfo = new VpnInfo();
@@ -7048,19 +7162,6 @@
         mService.getConnectionOwnerUid(getTestConnectionInfo());
     }
 
-    private TestNetworkAgentWrapper establishVpn(
-            LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception {
-        final TestNetworkAgentWrapper
-                vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp);
-        vpnNetworkAgent.getNetworkCapabilities().setOwnerUid(ownerUid);
-        mMockVpn.setNetworkAgent(vpnNetworkAgent);
-        mMockVpn.connect();
-        mMockVpn.setUids(vpnRange);
-        vpnNetworkAgent.connect(true);
-        waitForIdle();
-        return vpnNetworkAgent;
-    }
-
     private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
         final PackageInfo packageInfo = new PackageInfo();
         if (hasSystemPermission) {
@@ -7240,22 +7341,28 @@
 
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
-        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
 
         // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be
         // active
         final VpnInfo info = new VpnInfo();
         info.ownerUid = Process.myUid();
-        info.vpnIface = "interface";
+        info.vpnIface = VPN_IFNAME;
         mMockVpn.setVpnInfo(info);
-        mMockVpn.overrideUnderlyingNetworks(new Network[] {network});
+
+        mMockVpn.establishForMyUid();
+        waitForIdle();
+
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+
+        assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {network}));
         assertTrue(
                 "Active VPN permission not applied",
                 mService.checkConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
 
-        mMockVpn.overrideUnderlyingNetworks(null);
+        assertTrue(mService.setUnderlyingNetworksForVpn(null));
         assertFalse(
                 "VPN shouldn't receive callback on non-underlying network",
                 mService.checkConnectivityDiagnosticsPermissions(
@@ -7276,8 +7383,6 @@
                 Manifest.permission.ACCESS_FINE_LOCATION);
         mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
 
-        // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested
-        mMockVpn.disconnect();
         assertTrue(
                 "NetworkCapabilities administrator uid permission not applied",
                 mService.checkConnectivityDiagnosticsPermissions(
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f92c602..b760958 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1568,6 +1568,22 @@
     return true;
   }
 
+  ResourceEntry* ResolveTableEntry(LinkContext* context, ResourceTable* table,
+                                   Reference* reference) {
+    if (!reference || !reference->name) {
+      return nullptr;
+    }
+    auto name_ref = ResourceNameRef(reference->name.value());
+    if (name_ref.package.empty()) {
+      name_ref.package = context->GetCompilationPackage();
+    }
+    const auto search_result = table->FindResource(name_ref);
+    if (!search_result) {
+      return nullptr;
+    }
+    return search_result.value().entry;
+  }
+
   void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
     const xml::Element* application = manifest->root->FindChild("", "application");
     if (!application) {
@@ -1582,22 +1598,13 @@
 
     // Find the icon resource defined within the application.
     const auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
-    if (!icon_reference || !icon_reference->name) {
-      return;
-    }
-
-    auto icon_name = ResourceNameRef(icon_reference->name.value());
-    if (icon_name.package.empty()) {
-      icon_name.package = context_->GetCompilationPackage();
-    }
-
-    const auto icon_entry_result = table->FindResource(icon_name);
-    if (!icon_entry_result) {
+    const auto icon_entry = ResolveTableEntry(context_, table, icon_reference);
+    if (!icon_entry) {
       return;
     }
 
     int icon_max_sdk = 0;
-    for (auto& config_value : icon_entry_result.value().entry->values) {
+    for (auto& config_value : icon_entry->values) {
       icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion)
           ? config_value->config.sdkVersion : icon_max_sdk;
     }
@@ -1608,22 +1615,13 @@
 
     // Find the roundIcon resource defined within the application.
     const auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
-    if (!round_icon_reference || !round_icon_reference->name) {
-      return;
-    }
-
-    auto round_icon_name = ResourceNameRef(round_icon_reference->name.value());
-    if (round_icon_name.package.empty()) {
-      round_icon_name.package = context_->GetCompilationPackage();
-    }
-
-    const auto round_icon_entry_result = table->FindResource(round_icon_name);
-    if (!round_icon_entry_result) {
+    const auto round_icon_entry = ResolveTableEntry(context_, table, round_icon_reference);
+    if (!round_icon_entry) {
       return;
     }
 
     int round_icon_max_sdk = 0;
-    for (auto& config_value : round_icon_entry_result.value().entry->values) {
+    for (auto& config_value : round_icon_entry->values) {
       round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion)
                      ? config_value->config.sdkVersion : round_icon_max_sdk;
     }
@@ -1634,7 +1632,7 @@
     }
 
     // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
-    for (auto& config_value : icon_entry_result.value().entry->values) {
+    for (auto& config_value : icon_entry->values) {
       if (config_value->config.sdkVersion < SDK_O) {
         continue;
       }
@@ -1645,12 +1643,62 @@
                                                      << "\" for round icon compatibility");
 
       auto value = icon_reference->Clone(&table->string_pool);
-      auto round_config_value = round_icon_entry_result.value().entry->FindOrCreateValue(
-          config_value->config, config_value->product);
+      auto round_config_value =
+          round_icon_entry->FindOrCreateValue(config_value->config, config_value->product);
       round_config_value->value.reset(value);
     }
   }
 
+  bool VerifySharedUserId(xml::XmlResource* manifest, ResourceTable* table) {
+    const xml::Element* manifest_el = xml::FindRootElement(manifest->root.get());
+    if (manifest_el == nullptr) {
+      return true;
+    }
+    if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+      return true;
+    }
+    const xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "sharedUserId");
+    if (!attr) {
+      return true;
+    }
+    const auto validate = [&](const std::string& shared_user_id) -> bool {
+      if (util::IsAndroidSharedUserId(context_->GetCompilationPackage(), shared_user_id)) {
+        return true;
+      }
+      DiagMessage error_msg(manifest_el->line_number);
+      error_msg << "attribute 'sharedUserId' in <manifest> tag is not a valid shared user id: '"
+                << shared_user_id << "'";
+      if (options_.manifest_fixer_options.warn_validation) {
+        // Treat the error only as a warning.
+        context_->GetDiagnostics()->Warn(error_msg);
+        return true;
+      }
+      context_->GetDiagnostics()->Error(error_msg);
+      return false;
+    };
+    // If attr->compiled_value is not null, check if it is a ref
+    if (attr->compiled_value) {
+      const auto ref = ValueCast<Reference>(attr->compiled_value.get());
+      if (ref == nullptr) {
+        return true;
+      }
+      const auto shared_user_id_entry = ResolveTableEntry(context_, table, ref);
+      if (!shared_user_id_entry) {
+        return true;
+      }
+      for (const auto& value : shared_user_id_entry->values) {
+        const auto str_value = ValueCast<String>(value->value.get());
+        if (str_value != nullptr && !validate(*str_value->value)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // Fallback to checking the raw value
+    return validate(attr->value);
+  }
+
   // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
   // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
@@ -1672,6 +1720,11 @@
     // See (b/34829129)
     AliasAdaptiveIcon(manifest, table);
 
+    // Verify the shared user id here to handle the case of reference value.
+    if (!VerifySharedUserId(manifest, table)) {
+      return false;
+    }
+
     ResourceFileFlattenerOptions file_flattener_options;
     file_flattener_options.keep_raw_values = keep_raw_values;
     file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index fd3a4c0..c03661c 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -163,7 +163,8 @@
   return true;
 }
 
-static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy,
+                           SourcePathDiagnostics* diag) {
   xml::Attribute* attr = el->FindAttribute({}, "package");
   if (!attr) {
     diag->Error(DiagMessage(el->line_number)
@@ -174,10 +175,16 @@
                 << "attribute 'package' in <manifest> tag must not be a reference");
     return false;
   } else if (!util::IsAndroidPackageName(attr->value)) {
-    diag->Error(DiagMessage(el->line_number)
-                << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
-                << attr->value << "'");
-    return false;
+    DiagMessage error_msg(el->line_number);
+    error_msg << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
+              << attr->value << "'";
+    if (policy == xml::XmlActionExecutorPolicy::kAllowListWarning) {
+      // Treat the error only as a warning.
+      diag->Warn(error_msg);
+    } else {
+      diag->Error(error_msg);
+      return false;
+    }
   }
 
   attr = el->FindAttribute({}, "split");
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index ef33c34..28330db 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -38,6 +38,11 @@
 namespace aapt {
 namespace util {
 
+// Package name and shared user id would be used as a part of the file name.
+// Limits size to 223 and reserves 32 for the OS.
+// See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+constexpr static const size_t kMaxPackageNameSize = 223;
+
 static std::vector<std::string> SplitAndTransform(
     const StringPiece& str, char sep, const std::function<char(char)>& f) {
   std::vector<std::string> parts;
@@ -169,9 +174,21 @@
 }
 
 bool IsAndroidPackageName(const StringPiece& str) {
+  if (str.size() > kMaxPackageNameSize) {
+    return false;
+  }
   return IsAndroidNameImpl(str) > 1 || str == "android";
 }
 
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+                           const android::StringPiece& shared_user_id) {
+  if (shared_user_id.size() > kMaxPackageNameSize) {
+    return false;
+  }
+  return shared_user_id.empty() || IsAndroidNameImpl(shared_user_id) > 1 ||
+         package_name == "android";
+}
+
 bool IsAndroidSplitName(const StringPiece& str) {
   return IsAndroidNameImpl(str) > 0;
 }
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a956957..c77aca3 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -81,6 +81,7 @@
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 // - Package must contain at least two components, unless it is 'android'.
+// - The maximum package name length is 223.
 bool IsAndroidPackageName(const android::StringPiece& str);
 
 // Tests that the string is a valid Android split name.
@@ -88,6 +89,15 @@
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 bool IsAndroidSplitName(const android::StringPiece& str);
 
+// Tests that the string is a valid Android shared user id.
+// - First character of each component (separated by '.') must be an ASCII letter.
+// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
+// - Must contain at least two components, unless package name is 'android'.
+// - The maximum shared user id length is 223.
+// - Treat empty string as valid, it's the case of no shared user id.
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+                           const android::StringPiece& shared_user_id);
+
 // Converts the class name to a fully qualified class name from the given
 // `package`. Ex:
 //
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index d4e3bec..4ebcb11 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -27,6 +27,17 @@
 
 namespace aapt {
 
+// Test that a max package name size 223 is valid.
+static const std::string kMaxPackageName =
+    "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQux"
+    "SoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+    "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6l";
+// Test that a long package name size 224 is invalid.
+static const std::string kLongPackageName =
+    "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQu"
+    "xSoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+    "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6le";
+
 TEST(UtilTest, TrimOnlyWhitespace) {
   const StringPiece trimmed = util::TrimWhitespace("\n        ");
   EXPECT_TRUE(trimmed.empty());
@@ -108,6 +119,7 @@
   EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_thing"));
   EXPECT_TRUE(util::IsAndroidPackageName("com.foo.testing_thing_"));
   EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_99_"));
+  EXPECT_TRUE(util::IsAndroidPackageName(kMaxPackageName));
 
   EXPECT_FALSE(util::IsAndroidPackageName("android._test"));
   EXPECT_FALSE(util::IsAndroidPackageName("com"));
@@ -116,6 +128,27 @@
   EXPECT_FALSE(util::IsAndroidPackageName(".android"));
   EXPECT_FALSE(util::IsAndroidPackageName(".."));
   EXPECT_FALSE(util::IsAndroidPackageName("cøm.foo"));
+  EXPECT_FALSE(util::IsAndroidPackageName(kLongPackageName));
+}
+
+TEST(UtilTest, IsAndroidSharedUserId) {
+  EXPECT_TRUE(util::IsAndroidSharedUserId("android", "foo"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "android.test"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_thing"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.testing_thing_"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_99_"));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", ""));
+  EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", kMaxPackageName));
+
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android._test"));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "com"));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "_android"));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android."));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".android"));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".."));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "cøm.foo"));
+  EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", kLongPackageName));
 }
 
 TEST(UtilTest, FullyQualifiedClassName) {
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index fab17c9..ea42d26 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -21,23 +21,34 @@
 namespace aapt {
 namespace xml {
 
-static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
+static bool wrapper_one(const XmlNodeAction::ActionFunc& f, Element* el,
+                        const XmlActionExecutorPolicy& policy, SourcePathDiagnostics*) {
   return f(el);
 }
 
-static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
-                        SourcePathDiagnostics* diag) {
+static bool wrapper_two(const XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+                        const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
   return f(el, diag);
 }
 
+static bool wrapper_three(const XmlNodeAction::ActionFuncWithPolicyAndDiag& f, Element* el,
+                          const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
+  return f(el, policy, diag);
+}
+
 void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) {
-  actions_.emplace_back(std::bind(
-      wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2));
+  actions_.emplace_back(std::bind(wrapper_one, std::move(f), std::placeholders::_1,
+                                  std::placeholders::_2, std::placeholders::_3));
 }
 
 void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) {
-  actions_.emplace_back(std::bind(
-      wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2));
+  actions_.emplace_back(std::bind(wrapper_two, std::move(f), std::placeholders::_1,
+                                  std::placeholders::_2, std::placeholders::_3));
+}
+
+void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithPolicyAndDiag f) {
+  actions_.emplace_back(std::bind(wrapper_three, std::move(f), std::placeholders::_1,
+                                  std::placeholders::_2, std::placeholders::_3));
 }
 
 static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
@@ -51,8 +62,8 @@
 bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb,
                             SourcePathDiagnostics* diag, Element* el) const {
   bool error = false;
-  for (const ActionFuncWithDiag& action : actions_) {
-    error |= !action(el, diag);
+  for (const ActionFuncWithPolicyAndDiag& action : actions_) {
+    error |= !action(el, policy, diag);
   }
 
   for (Element* child_el : el->GetChildElements()) {
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index a0ad1da..78c4334 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -49,6 +49,8 @@
 // holds XmlNodeActions for child XML nodes.
 class XmlNodeAction {
  public:
+  using ActionFuncWithPolicyAndDiag =
+      std::function<bool(Element*, XmlActionExecutorPolicy, SourcePathDiagnostics*)>;
   using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
   using ActionFunc = std::function<bool(Element*)>;
 
@@ -61,6 +63,7 @@
   // Add an action to be performed at this XmlNodeAction.
   void Action(ActionFunc f);
   void Action(ActionFuncWithDiag);
+  void Action(ActionFuncWithPolicyAndDiag);
 
  private:
   friend class XmlActionExecutor;
@@ -69,7 +72,7 @@
                SourcePathDiagnostics* diag, Element* el) const;
 
   std::map<std::string, XmlNodeAction> map_;
-  std::vector<ActionFuncWithDiag> actions_;
+  std::vector<ActionFuncWithPolicyAndDiag> actions_;
 };
 
 // Allows the definition of actions to execute at specific XML elements defined by their hierarchy.
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index d235c80..dc96df6 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -89,8 +89,6 @@
 rule android.util.LocalLog* com.android.wifi.x.@0
 rule android.util.Rational* com.android.wifi.x.@0
 
-rule android.os.BasicShellCommandHandler* com.android.wifi.x.@0
-
 # Use our statically linked bouncy castle library
 rule org.bouncycastle.** com.android.wifi.x.@0
 # Use our statically linked protobuf library
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index e9d1a00..9f9d7f3 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -538,7 +538,7 @@
         if (!SdkLevel.isAtLeastS()) {
             throw new UnsupportedOperationException();
         }
-        return mChannels;
+        return mChannels.clone();
     }
 
     /**